From a641536e11ed32c0b0f84ad58134cd33d3d14f43 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 12 Jul 2023 15:00:53 +0200 Subject: [PATCH] io: expand docs. --- embedded-io-async/src/lib.rs | 74 +++++++++++++++++++++++++++++++++--- embedded-io/README.md | 8 +++- embedded-io/src/lib.rs | 69 +++++++++++++++++++++++++++++---- 3 files changed, 137 insertions(+), 14 deletions(-) diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs index d5a934ae2..c94e349a4 100644 --- a/embedded-io-async/src/lib.rs +++ b/embedded-io-async/src/lib.rs @@ -15,12 +15,41 @@ pub use embedded_io::{ /// Async reader. /// -/// Semantics are the same as [`std::io::Read`], check its documentation for details. +/// This trait is the `embedded-io-async` equivalent of [`std::io::Read`]. pub trait Read: ErrorType { - /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + /// Read some bytes from this source into the specified buffer, returning how many bytes were read. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If bytes are available, a non-zero amount of bytes is read to the beginning of `buf`, and the amount + /// is returned. It is not guaranteed that *all* available bytes are returned, it is possible for the + /// implementation to read an amount of bytes less than `buf.len()` while there are more bytes immediately + /// available. + /// + /// If the reader is at end-of-file (EOF), `Ok(0)` is returned. There is no guarantee that a reader at EOF + /// will always be so in the future, for example a reader can stop being at EOF if another process appends + /// more bytes to the underlying file. + /// + /// If `buf.len() == 0`, `read` returns without waiting, with either `Ok(0)` or an error. + /// The `Ok(0)` doesn't indicate EOF, unlike when called with a non-empty buffer. + /// + /// Implementations are encouraged to make this function side-effect-free on cancel (AKA "cancel-safe"), i.e. + /// guarantee that if you cancel (drop) a `read()` future that hasn't completed yet, the stream's + /// state hasn't changed (no bytes have been read). + /// + /// This is not a requirement to allow implementations that read into the user's buffer straight from + /// the hardware with e.g. DMA. + /// + /// Implementations should document whether they're actually side-effect-free on cancel or not. async fn read(&mut self, buf: &mut [u8]) -> Result; /// Read the exact number of bytes required to fill `buf`. + /// + /// This function calls `read()` in a loop until exactly `buf.len()` bytes have + /// been read, waiting if needed. + /// + /// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned + /// future that hasn't completed yet, some bytes might have already been read, which will get lost. async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { while !buf.is_empty() { match self.read(buf).await { @@ -39,9 +68,15 @@ pub trait Read: ErrorType { /// Async buffered reader. /// -/// Semantics are the same as [`std::io::BufRead`], check its documentation for details. +/// This trait is the `embedded-io-async` equivalent of [`std::io::BufRead`]. pub trait BufRead: ErrorType { /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. There is no guarantee that a reader at EOF + /// will always be so in the future, for example a reader can stop being at EOF if another process appends + /// more bytes to the underlying file. async fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. @@ -50,9 +85,32 @@ pub trait BufRead: ErrorType { /// Async writer. /// -/// Semantics are the same as [`std::io::Write`], check its documentation for details. +/// This trait is the `embedded-io-async` equivalent of [`std::io::Write`]. pub trait Write: ErrorType { /// Write a buffer into this writer, returning how many bytes were written. + /// + /// If the writer is not currently ready to accept more bytes (for example, its buffer is full), + /// this function waits until it is ready to accept least one byte. + /// + /// If it's ready to accept bytes, a non-zero amount of bytes is written from the beginning of `buf`, and the amount + /// is returned. It is not guaranteed that *all* available buffer space is filled, i.e. it is possible for the + /// implementation to write an amount of bytes less than `buf.len()` while the writer continues to be + /// ready to accept more bytes immediately. + /// + /// Implementations should never return `Ok(0)` when `buf.len() != 0`. Situations where the writer is not + /// able to accept more bytes and likely never will are better indicated with errors. + /// + /// If `buf.len() == 0`, `write` returns without waiting, with either `Ok(0)` or an error. + /// The `Ok(0)` doesn't indicate an error. + /// + /// Implementations are encouraged to make this function side-effect-free on cancel (AKA "cancel-safe"), i.e. + /// guarantee that if you cancel (drop) a `write()` future that hasn't completed yet, the stream's + /// state hasn't changed (no bytes have been written). + /// + /// This is not a requirement to allow implementations that write from the user's buffer straight to + /// the hardware with e.g. DMA. + /// + /// Implementations should document whether they're actually side-effect-free on cancel or not. async fn write(&mut self, buf: &[u8]) -> Result; /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. @@ -61,6 +119,12 @@ pub trait Write: ErrorType { } /// Write an entire buffer into this writer. + /// + /// This function calls `write()` in a loop until exactly `buf.len()` bytes have + /// been written, waiting if needed. + /// + /// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned + /// future that hasn't completed yet, some bytes might have already been written. async fn write_all(&mut self, buf: &[u8]) -> Result<(), WriteAllError> { let mut buf = buf; while !buf.is_empty() { @@ -76,7 +140,7 @@ pub trait Write: ErrorType { /// Async seek within streams. /// -/// Semantics are the same as [`std::io::Seek`], check its documentation for details. +/// This trait is the `embedded-io-async` equivalent of [`std::io::Seek`]. pub trait Seek: ErrorType { /// Seek to an offset, in bytes, in a stream. async fn seek(&mut self, pos: SeekFrom) -> Result; diff --git a/embedded-io/README.md b/embedded-io/README.md index 2563bdbf7..d579b09f6 100644 --- a/embedded-io/README.md +++ b/embedded-io/README.md @@ -12,8 +12,12 @@ Rust's `std::io` traits are not available in `no_std` targets, mainly because `s requires allocation. This crate contains replacement equivalent traits, usable in `no_std` targets. -The only difference with `std::io` is `Error` is an associated type. This allows each implementor -to return its own error type, while avoiding `dyn` or `Box`. This is how errors are handled in [`embedded-hal`](https://github.com/rust-embedded/embedded-hal/). +## Differences with `std::io` + +- `Error` is an associated type. This allows each implementor to return its own error type, +while avoiding `dyn` or `Box`. This is consistent with how errors are handled in [`embedded-hal`](https://github.com/rust-embedded/embedded-hal/). +- In `std::io`, the `Read`/`Write` traits might be blocking or non-blocking (i.e. returning `WouldBlock` errors) depending on the file descriptor's mode, which is only known at run-time. This allows passing a non-blocking stream to code that expects a blocking +stream, causing unexpected errors. To solve this, `embedded-io` specifies `Read`/`Write` are always blocking, and adds new `ReadReady`/`WriteReady` traits to allow using streams in a non-blocking way. ## Minimum Supported Rust Version (MSRV) diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index b71c43f5a..c514e055e 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -15,7 +15,7 @@ mod impls; /// Enumeration of possible methods to seek within an I/O object. /// -/// Semantics are the same as [`std::io::SeekFrom`], check its documentation for details. +/// This is the `embedded-io` equivalent of [`std::io::SeekFrom`]. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum SeekFrom { /// Sets the offset to the provided number of bytes. @@ -149,12 +149,33 @@ impl std::error::Error for WriteAllError {} /// Blocking reader. /// -/// Semantics are the same as [`std::io::Read`], check its documentation for details. +/// This trait is the `embedded-io` equivalent of [`std::io::Read`]. pub trait Read: crate::ErrorType { - /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + /// Read some bytes from this source into the specified buffer, returning how many bytes were read. + /// + /// If no bytes are currently available to read, this function blocks until at least one byte is available. + /// + /// If bytes are available, a non-zero amount of bytes is read to the beginning of `buf`, and the amount + /// is returned. It is not guaranteed that *all* available bytes are returned, it is possible for the + /// implementation to read an amount of bytes less than `buf.len()` while there are more bytes immediately + /// available. + /// + /// If the reader is at end-of-file (EOF), `Ok(0)` is returned. There is no guarantee that a reader at EOF + /// will always be so in the future, for example a reader can stop being at EOF if another process appends + /// more bytes to the underlying file. + /// + /// If `buf.len() == 0`, `read` returns without blocking, with either `Ok(0)` or an error. + /// The `Ok(0)` doesn't indicate EOF, unlike when called with a non-empty buffer. fn read(&mut self, buf: &mut [u8]) -> Result; /// Read the exact number of bytes required to fill `buf`. + /// + /// This function calls `read()` in a loop until exactly `buf.len()` bytes have + /// been read, blocking if needed. + /// + /// If you are using [`ReadReady`] to avoid blocking, you should not use this function. + /// `ReadReady::read_ready()` returning true only guarantees the first call to `read()` will + /// not block, so this function may still block in subsequent calls. fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { while !buf.is_empty() { match self.read(buf) { @@ -173,9 +194,15 @@ pub trait Read: crate::ErrorType { /// Blocking buffered reader. /// -/// Semantics are the same as [`std::io::BufRead`], check its documentation for details. +/// This trait is the `embedded-io` equivalent of [`std::io::BufRead`]. pub trait BufRead: crate::ErrorType { /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function blocks until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. There is no guarantee that a reader at EOF + /// will always be so in the future, for example a reader can stop being at EOF if another process appends + /// more bytes to the underlying file. fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. @@ -184,15 +211,36 @@ pub trait BufRead: crate::ErrorType { /// Blocking writer. /// -/// Semantics are the same as [`std::io::Write`], check its documentation for details. +/// This trait is the `embedded-io` equivalent of [`std::io::Write`]. pub trait Write: crate::ErrorType { /// Write a buffer into this writer, returning how many bytes were written. + /// + /// If the writer is not currently ready to accept more bytes (for example, its buffer is full), + /// this function blocks until it is ready to accept least one byte. + /// + /// If it's ready to accept bytes, a non-zero amount of bytes is written from the beginning of `buf`, and the amount + /// is returned. It is not guaranteed that *all* available buffer space is filled, i.e. it is possible for the + /// implementation to write an amount of bytes less than `buf.len()` while the writer continues to be + /// ready to accept more bytes immediately. + /// + /// Implementations should never return `Ok(0)` when `buf.len() != 0`. Situations where the writer is not + /// able to accept more bytes and likely never will are better indicated with errors. + /// + /// If `buf.len() == 0`, `write` returns without blocking, with either `Ok(0)` or an error. + /// The `Ok(0)` doesn't indicate an error. fn write(&mut self, buf: &[u8]) -> Result; - /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + /// Flush this output stream, blocking until all intermediately buffered contents reach their destination. fn flush(&mut self) -> Result<(), Self::Error>; /// Write an entire buffer into this writer. + /// + /// This function calls `write()` in a loop until exactly `buf.len()` bytes have + /// been written, blocking if needed. + /// + /// If you are using [`WriteReady`] to avoid blocking, you should not use this function. + /// `WriteReady::write_ready()` returning true only guarantees the first call to `write()` will + /// not block, so this function may still block in subsequent calls. fn write_all(&mut self, mut buf: &[u8]) -> Result<(), WriteAllError> { while !buf.is_empty() { match self.write(buf) { @@ -205,6 +253,13 @@ pub trait Write: crate::ErrorType { } /// Write a formatted string into this writer, returning any error encountered. + /// + /// This function calls `write()` in a loop until the entire formatted string has + /// been written, blocking if needed. + /// + /// If you are using [`WriteReady`] to avoid blocking, you should not use this function. + /// `WriteReady::write_ready()` returning true only guarantees the first call to `write()` will + /// not block, so this function may still block in subsequent calls. fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<(), WriteFmtError> { // Create a shim which translates a Write to a fmt::Write and saves // off I/O errors. instead of discarding them @@ -245,7 +300,7 @@ pub trait Write: crate::ErrorType { /// Blocking seek within streams. /// -/// Semantics are the same as [`std::io::Seek`], check its documentation for details. +/// This trait is the `embedded-io` equivalent of [`std::io::Seek`]. pub trait Seek: crate::ErrorType { /// Seek to an offset, in bytes, in a stream. fn seek(&mut self, pos: crate::SeekFrom) -> Result;