Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

io: expand docs. #469

Merged
merged 1 commit into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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