Skip to content

Commit

Permalink
io: expand docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
Dirbaio committed Jul 12, 2023
1 parent 7aaa585 commit a641536
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 14 deletions.
74 changes: 69 additions & 5 deletions embedded-io-async/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize, Self::Error>;

/// 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<Self::Error>> {
while !buf.is_empty() {
match self.read(buf).await {
Expand All @@ -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`.
Expand All @@ -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<usize, Self::Error>;

/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
Expand All @@ -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<Self::Error>> {
let mut buf = buf;
while !buf.is_empty() {
Expand All @@ -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<u64, Self::Error>;
Expand Down
8 changes: 6 additions & 2 deletions embedded-io/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
69 changes: 62 additions & 7 deletions embedded-io/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -149,12 +149,33 @@ impl<E: fmt::Debug> std::error::Error for WriteAllError<E> {}

/// 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<usize, Self::Error>;

/// 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<Self::Error>> {
while !buf.is_empty() {
match self.read(buf) {
Expand All @@ -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`.
Expand All @@ -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<usize, Self::Error>;

/// 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<Self::Error>> {
while !buf.is_empty() {
match self.write(buf) {
Expand All @@ -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<Self::Error>> {
// Create a shim which translates a Write to a fmt::Write and saves
// off I/O errors. instead of discarding them
Expand Down Expand Up @@ -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<u64, Self::Error>;
Expand Down

0 comments on commit a641536

Please sign in to comment.