From 6e27094671a116b3e1301a6fcb5c7bb0b9c8dae8 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 22:25:50 +0200 Subject: [PATCH 01/12] Add embedded-io. --- Cargo.toml | 1 + embedded-io/CHANGELOG.md | 34 +++++ embedded-io/Cargo.toml | 12 ++ embedded-io/LICENSE-APACHE | 201 ++++++++++++++++++++++++++ embedded-io/LICENSE-MIT | 25 ++++ embedded-io/README.md | 39 +++++ embedded-io/src/lib.rs | 281 +++++++++++++++++++++++++++++++++++++ 7 files changed, 593 insertions(+) create mode 100644 embedded-io/CHANGELOG.md create mode 100644 embedded-io/Cargo.toml create mode 100644 embedded-io/LICENSE-APACHE create mode 100644 embedded-io/LICENSE-MIT create mode 100644 embedded-io/README.md create mode 100644 embedded-io/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 4322dde4e..2a8ca15a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "embedded-hal-nb", "embedded-hal-bus", "embedded-can", + "embedded-io", ] diff --git a/embedded-io/CHANGELOG.md b/embedded-io/CHANGELOG.md new file mode 100644 index 000000000..43db9e22f --- /dev/null +++ b/embedded-io/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +- Moved `embedded_io::blocking` to the crate root. +- Split async traits to the `embedded-io-async` crate. +- Split async trait adapters to separate crates. + +## 0.4.0 - 2022-11-25 + +- Switch all traits to use [`async_fn_in_trait`](https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html) (AFIT). Requires `nightly-2022-11-22` or newer. + +## 0.3.1 - 2022-10-26 + +- Fix compilation on recent nightlies (#5) + +## 0.3.0 - 2022-05-19 + +- `FromFutures` adapter now requires `futures` Cargo feature. (breaking change) +- Add `FromTokio` adapter. +- Add blanket impls for `&mut T`, `Box`. +- Add impl `Read`, `BufRead` for `&[u8]` +- Add impl `Write` for `&mut [u8]` +- Add impl `Write` for `Vec` +- impl `std::error::Error` for `ReadExactError`, `WriteFmtError`. + +## 0.2.0 - 2022-05-07 + +- First release \ No newline at end of file diff --git a/embedded-io/Cargo.toml b/embedded-io/Cargo.toml new file mode 100644 index 000000000..71fe31203 --- /dev/null +++ b/embedded-io/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "embedded-io" +version = "0.5.0" +edition = "2021" +description = "Embedded IO traits" +repository = "https://github.com/rust-embedded/embedded-hal" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", +] diff --git a/embedded-io/LICENSE-APACHE b/embedded-io/LICENSE-APACHE new file mode 100644 index 000000000..16fe87b06 --- /dev/null +++ b/embedded-io/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embedded-io/LICENSE-MIT b/embedded-io/LICENSE-MIT new file mode 100644 index 000000000..e00608fbd --- /dev/null +++ b/embedded-io/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2023 The embedded-io authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embedded-io/README.md b/embedded-io/README.md new file mode 100644 index 000000000..33461b3cf --- /dev/null +++ b/embedded-io/README.md @@ -0,0 +1,39 @@ +[![crates.io](https://img.shields.io/crates/d/embedded-io.svg)](https://crates.io/crates/embedded-io) +[![crates.io](https://img.shields.io/crates/v/embedded-io.svg)](https://crates.io/crates/embedded-io) +[![Documentation](https://docs.rs/embedded-io/badge.svg)](https://docs.rs/embedded-io) + +# `embedded-io` + +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +IO traits for embedded systems. + +Rust's `std::io` traits are not available in `no_std` targets, mainly because `std::io::Error` +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/). + +## Minimum Supported Rust Version (MSRV) + +This crate is guaranteed to compile on stable Rust 1.59 and up. It *might* +compile with older versions but that may change in any new patch release. + +See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs new file mode 100644 index 000000000..4d90b5ea5 --- /dev/null +++ b/embedded-io/src/lib.rs @@ -0,0 +1,281 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +use core::fmt; + +/// Enumeration of possible methods to seek within an I/O object. +/// +/// Semantics are the same as [`std::io::SeekFrom`], check its documentation for details. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SeekFrom { + /// Sets the offset to the provided number of bytes. + Start(u64), + /// Sets the offset to the size of this object plus the specified number of bytes. + End(i64), + /// Sets the offset to the current position plus the specified number of bytes. + Current(i64), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +/// Possible kinds of errors. +pub enum ErrorKind { + /// Unspecified error kind. + Other, +} + +/// Error trait. +/// +/// This trait allows generic code to do limited inspecting of errors, +/// to react differently to different kinds. +pub trait Error: core::fmt::Debug { + /// Get the kind of this error. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +impl Error for ErrorKind { + fn kind(&self) -> ErrorKind { + *self + } +} + +/// Base trait for all IO traits. +/// +/// All IO operations of all traits return the error defined in this trait. +/// +/// Having a shared trait instead of having every trait define its own +/// `Error` associated type enforces all impls on the same type use the same error. +/// This is very convenient when writing generic code, it means you have to +/// handle a single error type `T::Error`, instead of `::Error` and `::Error` +/// which might be different types. +pub trait Io { + /// Error type of all the IO operations on this type. + type Error: Error; +} + +impl crate::Io for &mut T { + type Error = T::Error; +} + +/// Error returned by [`Read::read_exact`] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ReadExactError { + /// An EOF error was encountered before reading the exact amount of requested bytes. + UnexpectedEof, + /// Error returned by the inner Read. + Other(E), +} + +impl From for ReadExactError { + fn from(err: E) -> Self { + Self::Other(err) + } +} + +impl fmt::Display for ReadExactError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Error returned by [`Write::write_fmt`] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum WriteFmtError { + /// [`Write::write`] wrote zero bytes + WriteZero, + /// An error was encountered while formatting. + FmtError, + /// Error returned by the inner Write. + Other(E), +} + +impl From for WriteFmtError { + fn from(err: E) -> Self { + Self::Other(err) + } +} + +impl fmt::Display for WriteFmtError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Error returned by [`Write::write_all`] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum WriteAllError { + /// [`Write::write`] wrote zero bytes + WriteZero, + /// Error returned by the inner Write. + Other(E), +} + +impl From for WriteAllError { + fn from(err: E) -> Self { + Self::Other(err) + } +} + +impl fmt::Display for WriteAllError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Blocking reader. +/// +/// Semantics are the same as [`std::io::Read`], check its documentation for details. +pub trait Read: crate::Io { + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read the exact number of bytes required to fill `buf`. + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + while !buf.is_empty() { + match self.read(buf) { + Ok(0) => break, + Ok(n) => buf = &mut buf[n..], + Err(e) => return Err(ReadExactError::Other(e)), + } + } + if !buf.is_empty() { + Err(ReadExactError::UnexpectedEof) + } else { + Ok(()) + } + } +} + +/// Blocking buffered reader. +/// +/// Semantics are the same as [`std::io::BufRead`], check its documentation for details. +pub trait BufRead: crate::Io { + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + 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`. + fn consume(&mut self, amt: usize); +} + +/// Blocking writer. +/// +/// Semantics are the same as [`std::io::Write`], check its documentation for details. +pub trait Write: crate::Io { + /// Write a buffer into this writer, returning how many bytes were written. + fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + fn flush(&mut self) -> Result<(), Self::Error>; + + /// Write an entire buffer into this writer. + fn write_all(&mut self, mut buf: &[u8]) -> Result<(), WriteAllError> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => return Err(WriteAllError::WriteZero), + Ok(n) => buf = &buf[n..], + Err(e) => return Err(WriteAllError::Other(e)), + } + } + Ok(()) + } + + /// Write a formatted string into this writer, returning any error encountered. + 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 + struct Adapter<'a, T: Write + ?Sized + 'a> { + inner: &'a mut T, + error: Result<(), WriteAllError>, + } + + impl fmt::Write for Adapter<'_, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adapter { + inner: self, + error: Ok(()), + }; + match fmt::write(&mut output, fmt) { + Ok(()) => Ok(()), + Err(..) => match output.error { + // check if the error came from the underlying `Write` or not + Err(e) => match e { + WriteAllError::WriteZero => Err(WriteFmtError::WriteZero), + WriteAllError::Other(e) => Err(WriteFmtError::Other(e)), + }, + Ok(()) => Err(WriteFmtError::FmtError), + }, + } + } +} + +/// Blocking seek within streams. +/// +/// Semantics are the same as [`std::io::Seek`], check its documentation for details. +pub trait Seek: crate::Io { + /// Seek to an offset, in bytes, in a stream. + fn seek(&mut self, pos: crate::SeekFrom) -> Result; + + /// Rewind to the beginning of a stream. + fn rewind(&mut self) -> Result<(), Self::Error> { + self.seek(crate::SeekFrom::Start(0))?; + Ok(()) + } + + /// Returns the current seek position from the start of the stream. + fn stream_position(&mut self) -> Result { + self.seek(crate::SeekFrom::Current(0)) + } +} + +impl Read for &mut T { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf) + } +} + +impl BufRead for &mut T { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self) + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt) + } +} + +impl Write for &mut T { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self) + } +} + +impl Seek for &mut T { + #[inline] + fn seek(&mut self, pos: crate::SeekFrom) -> Result { + T::seek(self, pos) + } +} From 6eb30c50d1bf49dde55fe25d46d695c06ba50b5f Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 22:26:21 +0200 Subject: [PATCH 02/12] io: add impls for slices. --- embedded-io/src/impls/mod.rs | 2 ++ embedded-io/src/impls/slice_mut.rs | 31 ++++++++++++++++++++++ embedded-io/src/impls/slice_ref.rs | 41 ++++++++++++++++++++++++++++++ embedded-io/src/lib.rs | 2 ++ 4 files changed, 76 insertions(+) create mode 100644 embedded-io/src/impls/mod.rs create mode 100644 embedded-io/src/impls/slice_mut.rs create mode 100644 embedded-io/src/impls/slice_ref.rs diff --git a/embedded-io/src/impls/mod.rs b/embedded-io/src/impls/mod.rs new file mode 100644 index 000000000..7f4a6f802 --- /dev/null +++ b/embedded-io/src/impls/mod.rs @@ -0,0 +1,2 @@ +mod slice_mut; +mod slice_ref; diff --git a/embedded-io/src/impls/slice_mut.rs b/embedded-io/src/impls/slice_mut.rs new file mode 100644 index 000000000..cefaa296c --- /dev/null +++ b/embedded-io/src/impls/slice_mut.rs @@ -0,0 +1,31 @@ +use crate::{Io, Write}; +use core::mem; + +impl Io for &mut [u8] { + type Error = core::convert::Infallible; +} + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of +/// kind `ErrorKind::WriteZero`. +impl Write for &mut [u8] { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/embedded-io/src/impls/slice_ref.rs b/embedded-io/src/impls/slice_ref.rs new file mode 100644 index 000000000..73c98ab55 --- /dev/null +++ b/embedded-io/src/impls/slice_ref.rs @@ -0,0 +1,41 @@ +use crate::{BufRead, Io, Read}; + +impl Io for &[u8] { + type Error = core::convert::Infallible; +} + +/// Read is implemented for `&[u8]` by copying from the slice. +/// +/// Note that reading updates the slice to point to the yet unread part. +/// The slice will be empty when EOF is reached. +impl Read for &[u8] { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } +} + +impl BufRead for &[u8] { + #[inline] + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(*self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + *self = &self[amt..]; + } +} diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index 4d90b5ea5..6bafbbe53 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -4,6 +4,8 @@ use core::fmt; +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. From eb6e078b2688d59c7a5d2553a61bd1f70650f2af Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 22:31:01 +0200 Subject: [PATCH 03/12] io: add std/alloc features, add Box/Vec impls. --- embedded-io/Cargo.toml | 8 ++++++ embedded-io/src/impls/boxx.rs | 47 +++++++++++++++++++++++++++++++++++ embedded-io/src/impls/mod.rs | 5 ++++ embedded-io/src/impls/vec.rs | 21 ++++++++++++++++ embedded-io/src/lib.rs | 15 ++++++++++- 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 embedded-io/src/impls/boxx.rs create mode 100644 embedded-io/src/impls/vec.rs diff --git a/embedded-io/Cargo.toml b/embedded-io/Cargo.toml index 71fe31203..d1f82254f 100644 --- a/embedded-io/Cargo.toml +++ b/embedded-io/Cargo.toml @@ -10,3 +10,11 @@ categories = [ "embedded", "no-std", ] + +[features] +std = ["alloc"] +alloc = [] + +[package.metadata.docs.rs] +features = ["std"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-io/src/impls/boxx.rs b/embedded-io/src/impls/boxx.rs new file mode 100644 index 000000000..27bc1931c --- /dev/null +++ b/embedded-io/src/impls/boxx.rs @@ -0,0 +1,47 @@ +use crate::{BufRead, Io, Read, Seek, Write}; +use alloc::boxed::Box; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Io for Box { + type Error = T::Error; +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Read for Box { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl BufRead for Box { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self) + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Box { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Seek for Box { + #[inline] + fn seek(&mut self, pos: crate::SeekFrom) -> Result { + T::seek(self, pos) + } +} diff --git a/embedded-io/src/impls/mod.rs b/embedded-io/src/impls/mod.rs index 7f4a6f802..e79b9b8bf 100644 --- a/embedded-io/src/impls/mod.rs +++ b/embedded-io/src/impls/mod.rs @@ -1,2 +1,7 @@ mod slice_mut; mod slice_ref; + +#[cfg(feature = "alloc")] +mod boxx; +#[cfg(feature = "alloc")] +mod vec; diff --git a/embedded-io/src/impls/vec.rs b/embedded-io/src/impls/vec.rs new file mode 100644 index 000000000..8aa1b62d6 --- /dev/null +++ b/embedded-io/src/impls/vec.rs @@ -0,0 +1,21 @@ +use crate::{Io, Write}; +use alloc::vec::Vec; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Io for Vec { + type Error = core::convert::Infallible; +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Vec { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index 6bafbbe53..61f112832 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -1,9 +1,13 @@ -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] #![warn(missing_docs)] #![doc = include_str!("../README.md")] use core::fmt; +#[cfg(feature = "alloc")] +extern crate alloc; + mod impls; /// Enumeration of possible methods to seek within an I/O object. @@ -87,6 +91,9 @@ impl fmt::Display for ReadExactError { } } +#[cfg(feature = "std")] +impl std::error::Error for ReadExactError {} + /// Error returned by [`Write::write_fmt`] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum WriteFmtError { @@ -110,6 +117,9 @@ impl fmt::Display for WriteFmtError { } } +#[cfg(feature = "std")] +impl std::error::Error for WriteFmtError {} + /// Error returned by [`Write::write_all`] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum WriteAllError { @@ -131,6 +141,9 @@ impl fmt::Display for WriteAllError { } } +#[cfg(feature = "std")] +impl std::error::Error for WriteAllError {} + /// Blocking reader. /// /// Semantics are the same as [`std::io::Read`], check its documentation for details. From 1b5a858b1231185b60a6835c91581860c9ce6194 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 22:32:39 +0200 Subject: [PATCH 04/12] io: add adapters to/from std::io. --- embedded-io/src/adapters.rs | 161 ++++++++++++++++++++++++++++++++++++ embedded-io/src/lib.rs | 3 + 2 files changed, 164 insertions(+) create mode 100644 embedded-io/src/adapters.rs diff --git a/embedded-io/src/adapters.rs b/embedded-io/src/adapters.rs new file mode 100644 index 000000000..942d52455 --- /dev/null +++ b/embedded-io/src/adapters.rs @@ -0,0 +1,161 @@ +//! Adapters to/from `std::io` traits. +//! +//! To interoperate with `std::io`, wrap a type in one of these +//! adapters. +//! +//! There's no separate adapters for Read/ReadBuf/Write traits. Instead, a single +//! adapter implements the right traits based on what the inner type implements. +//! This allows adapting a `Read+Write`, for example. + +use crate::SeekFrom; + +/// Adapter from `std::io` traits. +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +#[derive(Clone)] +pub struct FromStd { + inner: T, +} + +impl FromStd { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl FromStd { + /// Borrow the inner object. + pub fn inner(&self) -> &T { + &self.inner + } + + /// Mutably borrow the inner object. + pub fn inner_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl crate::Io for FromStd { + type Error = std::io::Error; +} + +impl crate::Read for FromStd { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(buf) + } +} + +impl crate::BufRead for FromStd { + fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.fill_buf() + } + + fn consume(&mut self, amt: usize) { + self.inner.consume(amt) + } +} + +impl crate::Write for FromStd { + fn write(&mut self, buf: &[u8]) -> Result { + self.inner.write(buf) + } + fn flush(&mut self) -> Result<(), Self::Error> { + self.inner.flush() + } +} + +impl crate::Seek for FromStd { + fn seek(&mut self, pos: crate::SeekFrom) -> Result { + self.inner.seek(pos.into()) + } +} + +/// Adapter to `std::io` traits. +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub struct ToStd { + inner: T, +} + +impl ToStd { + /// Create a new adapter. + pub fn new(inner: T) -> Self { + Self { inner } + } + + /// Consume the adapter, returning the inner object. + pub fn into_inner(self) -> T { + self.inner + } +} + +impl ToStd { + /// Borrow the inner object. + pub fn inner(&self) -> &T { + &self.inner + } + + /// Mutably borrow the inner object. + pub fn inner_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl std::io::Read for ToStd { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(buf).map_err(to_io_error) + } +} + +impl std::io::Write for ToStd { + fn write(&mut self, buf: &[u8]) -> Result { + self.inner.write(buf).map_err(to_io_error) + } + fn flush(&mut self) -> Result<(), std::io::Error> { + self.inner.flush().map_err(to_io_error) + } +} + +impl std::io::Seek for ToStd { + fn seek(&mut self, pos: std::io::SeekFrom) -> Result { + self.inner.seek(pos.into()).map_err(to_io_error) + } +} + +fn to_io_error(err: T) -> std::io::Error { + let kind = std::io::ErrorKind::Other; + std::io::Error::new(kind, format!("{:?}", err)) +} + +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl crate::Error for std::io::Error { + fn kind(&self) -> crate::ErrorKind { + crate::ErrorKind::Other + } +} + +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for std::io::SeekFrom { + fn from(pos: SeekFrom) -> Self { + match pos { + SeekFrom::Start(n) => std::io::SeekFrom::Start(n), + SeekFrom::End(n) => std::io::SeekFrom::End(n), + SeekFrom::Current(n) => std::io::SeekFrom::Current(n), + } + } +} + +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From for SeekFrom { + fn from(pos: std::io::SeekFrom) -> SeekFrom { + match pos { + std::io::SeekFrom::Start(n) => SeekFrom::Start(n), + std::io::SeekFrom::End(n) => SeekFrom::End(n), + std::io::SeekFrom::Current(n) => SeekFrom::Current(n), + } + } +} diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index 61f112832..07bf60517 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -8,6 +8,9 @@ use core::fmt; #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] +pub mod adapters; + mod impls; /// Enumeration of possible methods to seek within an I/O object. From d6f641995fc1597920b7d86bf58d0332b7ebbaeb Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 22:39:53 +0200 Subject: [PATCH 05/12] Add embedded-io-async. --- Cargo.toml | 1 + embedded-io-async/Cargo.toml | 15 +++++ embedded-io-async/README.md | 35 ++++++++++ embedded-io-async/src/lib.rs | 123 +++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 embedded-io-async/Cargo.toml create mode 100644 embedded-io-async/README.md create mode 100644 embedded-io-async/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 2a8ca15a9..ab7723059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ members = [ "embedded-hal-bus", "embedded-can", "embedded-io", + "embedded-io-async", # nightly-only ] diff --git a/embedded-io-async/Cargo.toml b/embedded-io-async/Cargo.toml new file mode 100644 index 000000000..e900278f9 --- /dev/null +++ b/embedded-io-async/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "embedded-io-async" +version = "0.5.0" +edition = "2021" +description = "Async embedded IO traits" +repository = "https://github.com/rust-embedded/embedded-hal" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", +] + +[dependencies] +embedded-io = { version = "0.5", path = "../embedded-io" } diff --git a/embedded-io-async/README.md b/embedded-io-async/README.md new file mode 100644 index 000000000..557a0866a --- /dev/null +++ b/embedded-io-async/README.md @@ -0,0 +1,35 @@ +[![crates.io](https://img.shields.io/crates/d/embedded-io-async.svg)](https://crates.io/crates/embedded-io-async) +[![crates.io](https://img.shields.io/crates/v/embedded-io-async.svg)](https://crates.io/crates/embedded-io-async) +[![Documentation](https://docs.rs/embedded-io-async/badge.svg)](https://docs.rs/embedded-io-async) + +# `embedded-io-async` + +Async IO traits for embedded systems. + +This crate contains asynchronous versions of the [`embedded-io`](https://crates.io/crates/embedded-io) traits and shares its scope and design goals. + +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +## Minimum Supported Rust Version (MSRV) + +This crate requires Rust nightly newer than `nightly-2022-11-22`, due to requiring support for +`async fn` in traits (AFIT), which is not stable yet. + +Keep in mind Rust nightlies can make backwards-incompatible changes to unstable features +at any time. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs new file mode 100644 index 000000000..8bf4fd26f --- /dev/null +++ b/embedded-io-async/src/lib.rs @@ -0,0 +1,123 @@ +#![no_std] +#![feature(async_fn_in_trait, impl_trait_projections)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +pub use embedded_io::{Error, ErrorKind, Io, ReadExactError, SeekFrom, WriteAllError}; + +/// Async reader. +/// +/// Semantics are the same as [`std::io::Read`], check its documentation for details. +pub trait Read: Io { + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + async fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read the exact number of bytes required to fill `buf`. + async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), ReadExactError> { + while !buf.is_empty() { + match self.read(buf).await { + Ok(0) => break, + Ok(n) => buf = &mut buf[n..], + Err(e) => return Err(ReadExactError::Other(e)), + } + } + if !buf.is_empty() { + Err(ReadExactError::UnexpectedEof) + } else { + Ok(()) + } + } +} + +/// Async buffered reader. +/// +/// Semantics are the same as [`std::io::BufRead`], check its documentation for details. +pub trait BufRead: Io { + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + 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`. + fn consume(&mut self, amt: usize); +} + +/// Async writer. +/// +/// Semantics are the same as [`std::io::Write`], check its documentation for details. +pub trait Write: Io { + /// Write a buffer into this writer, returning how many bytes were written. + async fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + /// Write an entire buffer into this writer. + async fn write_all(&mut self, buf: &[u8]) -> Result<(), WriteAllError> { + let mut buf = buf; + while !buf.is_empty() { + match self.write(buf).await { + Ok(0) => return Err(WriteAllError::WriteZero), + Ok(n) => buf = &buf[n..], + Err(e) => return Err(WriteAllError::Other(e)), + } + } + Ok(()) + } +} + +/// Async seek within streams. +/// +/// Semantics are the same as [`std::io::Seek`], check its documentation for details. +pub trait Seek: Io { + /// Seek to an offset, in bytes, in a stream. + async fn seek(&mut self, pos: SeekFrom) -> Result; + + /// Rewind to the beginning of a stream. + async fn rewind(&mut self) -> Result<(), Self::Error> { + self.seek(SeekFrom::Start(0)).await?; + Ok(()) + } + + /// Returns the current seek position from the start of the stream. + async fn stream_position(&mut self) -> Result { + self.seek(SeekFrom::Current(0)).await + } +} + +impl Read for &mut T { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf).await + } +} + +impl BufRead for &mut T { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self).await + } + + fn consume(&mut self, amt: usize) { + T::consume(self, amt) + } +} + +impl Write for &mut T { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf).await + } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self).await + } +} + +impl Seek for &mut T { + #[inline] + async fn seek(&mut self, pos: SeekFrom) -> Result { + T::seek(self, pos).await + } +} From a105876f16045ae37b67e19f67c672112164c516 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 22:58:10 +0200 Subject: [PATCH 06/12] io-async: add impls for slices. --- embedded-io-async/src/impls/mod.rs | 2 ++ embedded-io-async/src/impls/slice_mut.rs | 22 ++++++++++++++ embedded-io-async/src/impls/slice_ref.rs | 37 ++++++++++++++++++++++++ embedded-io-async/src/lib.rs | 2 ++ 4 files changed, 63 insertions(+) create mode 100644 embedded-io-async/src/impls/mod.rs create mode 100644 embedded-io-async/src/impls/slice_mut.rs create mode 100644 embedded-io-async/src/impls/slice_ref.rs diff --git a/embedded-io-async/src/impls/mod.rs b/embedded-io-async/src/impls/mod.rs new file mode 100644 index 000000000..7f4a6f802 --- /dev/null +++ b/embedded-io-async/src/impls/mod.rs @@ -0,0 +1,2 @@ +mod slice_mut; +mod slice_ref; diff --git a/embedded-io-async/src/impls/slice_mut.rs b/embedded-io-async/src/impls/slice_mut.rs new file mode 100644 index 000000000..4a195e3f4 --- /dev/null +++ b/embedded-io-async/src/impls/slice_mut.rs @@ -0,0 +1,22 @@ +use crate::Write; +use core::mem; + +/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting +/// its data. +/// +/// Note that writing updates the slice to point to the yet unwritten part. +/// The slice will be empty when it has been completely overwritten. +/// +/// If the number of bytes to be written exceeds the size of the slice, write operations will +/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of +/// kind `ErrorKind::WriteZero`. +impl Write for &mut [u8] { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = mem::take(self).split_at_mut(amt); + a.copy_from_slice(&buf[..amt]); + *self = b; + Ok(amt) + } +} diff --git a/embedded-io-async/src/impls/slice_ref.rs b/embedded-io-async/src/impls/slice_ref.rs new file mode 100644 index 000000000..a6a4ba807 --- /dev/null +++ b/embedded-io-async/src/impls/slice_ref.rs @@ -0,0 +1,37 @@ +use crate::{BufRead, Read}; + +/// Read is implemented for `&[u8]` by copying from the slice. +/// +/// Note that reading updates the slice to point to the yet unread part. +/// The slice will be empty when EOF is reached. +impl Read for &[u8] { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + let amt = core::cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } +} + +impl BufRead for &[u8] { + #[inline] + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(*self) + } + + #[inline] + fn consume(&mut self, amt: usize) { + *self = &self[amt..]; + } +} diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs index 8bf4fd26f..0e120c40c 100644 --- a/embedded-io-async/src/lib.rs +++ b/embedded-io-async/src/lib.rs @@ -4,6 +4,8 @@ #![warn(missing_docs)] #![doc = include_str!("../README.md")] +mod impls; + pub use embedded_io::{Error, ErrorKind, Io, ReadExactError, SeekFrom, WriteAllError}; /// Async reader. From 2c818b8b09aee0014a2b88e4997bd3a01e5f5a07 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 23:00:22 +0200 Subject: [PATCH 07/12] io-async: add std/alloc features, add Box/Vec impls. --- embedded-io-async/Cargo.toml | 8 ++++++ embedded-io-async/src/impls/boxx.rs | 44 +++++++++++++++++++++++++++++ embedded-io-async/src/impls/mod.rs | 5 ++++ embedded-io-async/src/impls/vec.rs | 12 ++++++++ embedded-io-async/src/lib.rs | 5 +++- 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 embedded-io-async/src/impls/boxx.rs create mode 100644 embedded-io-async/src/impls/vec.rs diff --git a/embedded-io-async/Cargo.toml b/embedded-io-async/Cargo.toml index e900278f9..e8f19af39 100644 --- a/embedded-io-async/Cargo.toml +++ b/embedded-io-async/Cargo.toml @@ -11,5 +11,13 @@ categories = [ "no-std", ] +[features] +std = ["alloc", "embedded-io/std"] +alloc = ["embedded-io/alloc"] + [dependencies] embedded-io = { version = "0.5", path = "../embedded-io" } + +[package.metadata.docs.rs] +features = ["std"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/embedded-io-async/src/impls/boxx.rs b/embedded-io-async/src/impls/boxx.rs new file mode 100644 index 000000000..677bf328d --- /dev/null +++ b/embedded-io-async/src/impls/boxx.rs @@ -0,0 +1,44 @@ +use crate::{BufRead, Read, Seek, SeekFrom, Write}; +use alloc::boxed::Box; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Read for Box { + #[inline] + async fn read(&mut self, buf: &mut [u8]) -> Result { + T::read(self, buf).await + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl BufRead for Box { + #[inline] + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + T::fill_buf(self).await + } + + #[inline] + fn consume(&mut self, amt: usize) { + T::consume(self, amt) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Box { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + T::write(self, buf).await + } + + #[inline] + async fn flush(&mut self) -> Result<(), Self::Error> { + T::flush(self).await + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Seek for Box { + #[inline] + async fn seek(&mut self, pos: SeekFrom) -> Result { + T::seek(self, pos).await + } +} diff --git a/embedded-io-async/src/impls/mod.rs b/embedded-io-async/src/impls/mod.rs index 7f4a6f802..e79b9b8bf 100644 --- a/embedded-io-async/src/impls/mod.rs +++ b/embedded-io-async/src/impls/mod.rs @@ -1,2 +1,7 @@ mod slice_mut; mod slice_ref; + +#[cfg(feature = "alloc")] +mod boxx; +#[cfg(feature = "alloc")] +mod vec; diff --git a/embedded-io-async/src/impls/vec.rs b/embedded-io-async/src/impls/vec.rs new file mode 100644 index 000000000..c24405e39 --- /dev/null +++ b/embedded-io-async/src/impls/vec.rs @@ -0,0 +1,12 @@ +use alloc::vec::Vec; + +use crate::Write; + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl Write for Vec { + #[inline] + async fn write(&mut self, buf: &[u8]) -> Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } +} diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs index 0e120c40c..8ef951450 100644 --- a/embedded-io-async/src/lib.rs +++ b/embedded-io-async/src/lib.rs @@ -1,9 +1,12 @@ -#![no_std] #![feature(async_fn_in_trait, impl_trait_projections)] +#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] #![warn(missing_docs)] #![doc = include_str!("../README.md")] +#[cfg(feature = "alloc")] +extern crate alloc; + mod impls; pub use embedded_io::{Error, ErrorKind, Io, ReadExactError, SeekFrom, WriteAllError}; From 931dd37ad66ad6588dcbbec9481e5f9323719e0a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 23:05:19 +0200 Subject: [PATCH 08/12] Remove serial traits (blocking and async). --- embedded-hal-async/src/lib.rs | 1 - embedded-hal-async/src/serial.rs | 27 --------- embedded-hal-nb/src/serial.rs | 76 ++++++++++++++++++++++++- embedded-hal/src/lib.rs | 1 - embedded-hal/src/serial.rs | 98 -------------------------------- 5 files changed, 73 insertions(+), 130 deletions(-) delete mode 100644 embedded-hal-async/src/serial.rs delete mode 100644 embedded-hal/src/serial.rs diff --git a/embedded-hal-async/src/lib.rs b/embedded-hal-async/src/lib.rs index 83410cdb9..37a81c0ff 100644 --- a/embedded-hal-async/src/lib.rs +++ b/embedded-hal-async/src/lib.rs @@ -7,5 +7,4 @@ pub mod delay; pub mod digital; pub mod i2c; -pub mod serial; pub mod spi; diff --git a/embedded-hal-async/src/serial.rs b/embedded-hal-async/src/serial.rs deleted file mode 100644 index 60ee1bc05..000000000 --- a/embedded-hal-async/src/serial.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Serial interface - -pub use embedded_hal::serial::{Error, ErrorKind, ErrorType}; - -/// Write half of a serial interface -pub trait Write: ErrorType { - /// Writes a slice, blocking until everything has been written. - /// - /// An implementation can choose to buffer the write, returning `Ok(())` - /// after the complete slice has been written to a buffer, but before all - /// words have been sent via the serial interface. To make sure that - /// everything has been sent, call [`flush`](Write::flush) after this function returns. - async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; - - /// Ensures that none of the previously written data is still buffered - async fn flush(&mut self) -> Result<(), Self::Error>; -} - -impl, Word: 'static + Copy> Write for &mut T { - async fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { - T::write(self, words).await - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - T::flush(self).await - } -} diff --git a/embedded-hal-nb/src/serial.rs b/embedded-hal-nb/src/serial.rs index e01803ab1..c069c1441 100644 --- a/embedded-hal-nb/src/serial.rs +++ b/embedded-hal-nb/src/serial.rs @@ -1,6 +1,77 @@ //! Serial interface -pub use embedded_hal::serial::{Error, ErrorKind, ErrorType}; +/// Serial error +pub trait Error: core::fmt::Debug { + /// Convert error to a generic serial error kind + /// + /// By using this method, serial errors freely defined by HAL implementations + /// can be converted to a set of generic serial errors upon which generic + /// code can act. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +/// Serial error kind +/// +/// This represents a common set of serial operation errors. HAL implementations are +/// free to define more specific or additional error types. However, by providing +/// a mapping to these common serial errors, generic code can still react to them. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[non_exhaustive] +pub enum ErrorKind { + /// The peripheral receive buffer was overrun. + Overrun, + /// Received data does not conform to the peripheral configuration. + /// Can be caused by a misconfigured device on either end of the serial line. + FrameFormat, + /// Parity check failed. + Parity, + /// Serial line is too noisy to read valid data. + Noise, + /// A different error occurred. The original error may contain more information. + Other, +} + +impl Error for ErrorKind { + fn kind(&self) -> ErrorKind { + *self + } +} + +impl core::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Overrun => write!(f, "The peripheral receive buffer was overrun"), + Self::Parity => write!(f, "Parity check failed"), + Self::Noise => write!(f, "Serial line is too noisy to read valid data"), + Self::FrameFormat => write!( + f, + "Received data does not conform to the peripheral configuration" + ), + Self::Other => write!( + f, + "A different error occurred. The original error may contain more information" + ), + } + } +} + +/// Serial error type trait +/// +/// This just defines the error type, to be used by the other traits. +pub trait ErrorType { + /// Error type + type Error: Error; +} + +impl ErrorType for &mut T { + type Error = T::Error; +} /// Read half of a serial interface /// @@ -40,8 +111,7 @@ impl, Word: Copy> Write for &mut T { /// /// TODO write example of usage -impl core::fmt::Write - for dyn Write + '_ +impl core::fmt::Write for dyn Write + '_ where Word: Copy + From, { diff --git a/embedded-hal/src/lib.rs b/embedded-hal/src/lib.rs index def14dbc0..ac09042b3 100644 --- a/embedded-hal/src/lib.rs +++ b/embedded-hal/src/lib.rs @@ -81,7 +81,6 @@ pub mod delay; pub mod digital; pub mod i2c; pub mod pwm; -pub mod serial; pub mod spi; mod private { diff --git a/embedded-hal/src/serial.rs b/embedded-hal/src/serial.rs deleted file mode 100644 index ab9be722b..000000000 --- a/embedded-hal/src/serial.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Serial traits - -/// Serial error -pub trait Error: core::fmt::Debug { - /// Convert error to a generic serial error kind - /// - /// By using this method, serial errors freely defined by HAL implementations - /// can be converted to a set of generic serial errors upon which generic - /// code can act. - fn kind(&self) -> ErrorKind; -} - -impl Error for core::convert::Infallible { - fn kind(&self) -> ErrorKind { - match *self {} - } -} - -/// Serial error kind -/// -/// This represents a common set of serial operation errors. HAL implementations are -/// free to define more specific or additional error types. However, by providing -/// a mapping to these common serial errors, generic code can still react to them. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[non_exhaustive] -pub enum ErrorKind { - /// The peripheral receive buffer was overrun. - Overrun, - /// Received data does not conform to the peripheral configuration. - /// Can be caused by a misconfigured device on either end of the serial line. - FrameFormat, - /// Parity check failed. - Parity, - /// Serial line is too noisy to read valid data. - Noise, - /// A different error occurred. The original error may contain more information. - Other, -} - -impl Error for ErrorKind { - fn kind(&self) -> ErrorKind { - *self - } -} - -impl core::fmt::Display for ErrorKind { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Overrun => write!(f, "The peripheral receive buffer was overrun"), - Self::Parity => write!(f, "Parity check failed"), - Self::Noise => write!(f, "Serial line is too noisy to read valid data"), - Self::FrameFormat => write!( - f, - "Received data does not conform to the peripheral configuration" - ), - Self::Other => write!( - f, - "A different error occurred. The original error may contain more information" - ), - } - } -} - -/// Serial error type trait -/// -/// This just defines the error type, to be used by the other traits. -pub trait ErrorType { - /// Error type - type Error: Error; -} - -impl ErrorType for &mut T { - type Error = T::Error; -} - -/// Write half of a serial interface (blocking variant) -pub trait Write: ErrorType { - /// Writes a slice, blocking until everything has been written - /// - /// An implementation can choose to buffer the write, returning `Ok(())` - /// after the complete slice has been written to a buffer, but before all - /// words have been sent via the serial interface. To make sure that - /// everything has been sent, call [`flush`](Write::flush) after this function returns. - fn write(&mut self, buffer: &[Word]) -> Result<(), Self::Error>; - - /// Block until the serial interface has sent all buffered words - fn flush(&mut self) -> Result<(), Self::Error>; -} - -impl, Word: Copy> Write for &mut T { - fn write(&mut self, buffer: &[Word]) -> Result<(), Self::Error> { - T::write(self, buffer) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - T::flush(self) - } -} From 31b62d3a0775b2d4b5ea45e9500259f02af26daa Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 5 Jul 2023 00:09:19 +0200 Subject: [PATCH 09/12] io: add ReadReady, WriteReady. --- embedded-io-async/src/lib.rs | 4 +++- embedded-io/CHANGELOG.md | 1 + embedded-io/src/impls/boxx.rs | 18 ++++++++++++++- embedded-io/src/lib.rs | 41 +++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs index 8ef951450..f204eada5 100644 --- a/embedded-io-async/src/lib.rs +++ b/embedded-io-async/src/lib.rs @@ -9,7 +9,9 @@ extern crate alloc; mod impls; -pub use embedded_io::{Error, ErrorKind, Io, ReadExactError, SeekFrom, WriteAllError}; +pub use embedded_io::{ + Error, ErrorKind, Io, ReadExactError, ReadReady, SeekFrom, WriteAllError, WriteReady, +}; /// Async reader. /// diff --git a/embedded-io/CHANGELOG.md b/embedded-io/CHANGELOG.md index 43db9e22f..6af13a646 100644 --- a/embedded-io/CHANGELOG.md +++ b/embedded-io/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Added `ReadReady`, `WriteReady` traits. They allow peeking whether the i/o handle is ready to read/write, so they allow using the traits in a non-blocking way. - Moved `embedded_io::blocking` to the crate root. - Split async traits to the `embedded-io-async` crate. - Split async trait adapters to separate crates. diff --git a/embedded-io/src/impls/boxx.rs b/embedded-io/src/impls/boxx.rs index 27bc1931c..727d1f21c 100644 --- a/embedded-io/src/impls/boxx.rs +++ b/embedded-io/src/impls/boxx.rs @@ -1,4 +1,4 @@ -use crate::{BufRead, Io, Read, Seek, Write}; +use crate::{BufRead, Io, Read, ReadReady, Seek, Write, WriteReady}; use alloc::boxed::Box; #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] @@ -45,3 +45,19 @@ impl Seek for Box { T::seek(self, pos) } } + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl ReadReady for Box { + #[inline] + fn read_ready(&mut self) -> Result { + T::read_ready(self) + } +} + +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +impl WriteReady for Box { + #[inline] + fn write_ready(&mut self) -> Result { + T::write_ready(self) + } +} diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index 07bf60517..7f7aa2eb9 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -262,6 +262,33 @@ pub trait Seek: crate::Io { } } +/// Get whether a reader is ready. +/// +/// This allows using a [`Read`] or [`BufRead`] in a nonblocking fashion, i.e. trying to read +/// only when it is ready. +pub trait ReadReady: crate::Io { + /// Get whether the reader is ready for immediately reading. + /// + /// This usually means that there is either some bytes have been received and are buffered and ready to be read, + /// or that the reader is at EOF. + /// + /// If this returns `true`, it's guaranteed that the next call to [`Read::read`] or [`BufRead::fill_buf`] will not block. + fn read_ready(&mut self) -> Result; +} + +/// Get whether a writer is ready. +/// +/// This allows using a [`Write`] in a nonblocking fashion, i.e. trying to write +/// only when it is ready. +pub trait WriteReady: crate::Io { + /// Get whether the writer is ready for immediately writeing. + /// + /// This usually means that there is free space in the internal transmit buffer. + /// + /// If this returns `true`, it's guaranteed that the next call to [`Write::write`] will not block. + fn write_ready(&mut self) -> Result; +} + impl Read for &mut T { #[inline] fn read(&mut self, buf: &mut [u8]) -> Result { @@ -297,3 +324,17 @@ impl Seek for &mut T { T::seek(self, pos) } } + +impl ReadReady for &mut T { + #[inline] + fn read_ready(&mut self) -> Result { + T::read_ready(self) + } +} + +impl WriteReady for &mut T { + #[inline] + fn write_ready(&mut self) -> Result { + T::write_ready(self) + } +} From 5df51e56dae11fc4fbe1bb9fc6ed08ceb1958e2c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 4 Jul 2023 23:02:02 +0200 Subject: [PATCH 10/12] ci: test io with std and alloc features, update nightly. --- .github/workflows/clippy.yml | 4 ++-- .github/workflows/test.yml | 4 +++- embedded-hal-async/src/lib.rs | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 20cd030f6..bdec38346 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -15,6 +15,6 @@ jobs: with: # embedded-hal-async needs nightly. # Use a pinned version to avoid spontaneous breakages (new clippy lints are added often) - toolchain: nightly-2022-11-22 + toolchain: nightly-2023-07-03 components: clippy - - run: cargo clippy --features=embedded-hal-bus/std -- --deny=warnings \ No newline at end of file + - run: cargo clippy --features=std -- --deny=warnings \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1677ea895..e50f0c6a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,9 @@ jobs: - thumbv7m-none-eabi include: - target: x86_64-unknown-linux-gnu - features: embedded-hal-bus/std + features: std + - target: x86_64-unknown-linux-gnu + features: alloc steps: - uses: actions/checkout@v3 diff --git a/embedded-hal-async/src/lib.rs b/embedded-hal-async/src/lib.rs index 37a81c0ff..5fe9168e7 100644 --- a/embedded-hal-async/src/lib.rs +++ b/embedded-hal-async/src/lib.rs @@ -1,7 +1,6 @@ #![doc = include_str!("../README.md")] #![warn(missing_docs)] #![no_std] -#![allow(incomplete_features)] #![feature(async_fn_in_trait, impl_trait_projections)] pub mod delay; From e36386845ce7fc6969e0ea5b3ae5042113e92df6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 12 Jul 2023 12:48:27 +0200 Subject: [PATCH 11/12] Apply suggestions from code review Co-authored-by: Diego Barrios Romero --- embedded-io/CHANGELOG.md | 2 +- embedded-io/README.md | 2 +- embedded-io/src/adapters.rs | 14 +++++++------- embedded-io/src/lib.rs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/embedded-io/CHANGELOG.md b/embedded-io/CHANGELOG.md index 6af13a646..f7043a7de 100644 --- a/embedded-io/CHANGELOG.md +++ b/embedded-io/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- Added `ReadReady`, `WriteReady` traits. They allow peeking whether the i/o handle is ready to read/write, so they allow using the traits in a non-blocking way. +- Added `ReadReady`, `WriteReady` traits. They allow peeking whether the I/O handle is ready to read/write, so they allow using the traits in a non-blocking way. - Moved `embedded_io::blocking` to the crate root. - Split async traits to the `embedded-io-async` crate. - Split async trait adapters to separate crates. diff --git a/embedded-io/README.md b/embedded-io/README.md index 33461b3cf..2563bdbf7 100644 --- a/embedded-io/README.md +++ b/embedded-io/README.md @@ -6,7 +6,7 @@ This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). -IO traits for embedded systems. +Input/Output traits for embedded systems. Rust's `std::io` traits are not available in `no_std` targets, mainly because `std::io::Error` requires allocation. This crate contains replacement equivalent traits, usable in `no_std` diff --git a/embedded-io/src/adapters.rs b/embedded-io/src/adapters.rs index 942d52455..741e243d0 100644 --- a/embedded-io/src/adapters.rs +++ b/embedded-io/src/adapters.rs @@ -3,9 +3,9 @@ //! To interoperate with `std::io`, wrap a type in one of these //! adapters. //! -//! There's no separate adapters for Read/ReadBuf/Write traits. Instead, a single +//! There are no separate adapters for `Read`/`ReadBuf`/`Write` traits. Instead, a single //! adapter implements the right traits based on what the inner type implements. -//! This allows adapting a `Read+Write`, for example. +//! This allows using these adapters when using `Read+Write`, for example. use crate::SeekFrom; @@ -107,26 +107,26 @@ impl ToStd { impl std::io::Read for ToStd { fn read(&mut self, buf: &mut [u8]) -> Result { - self.inner.read(buf).map_err(to_io_error) + self.inner.read(buf).map_err(to_std_error) } } impl std::io::Write for ToStd { fn write(&mut self, buf: &[u8]) -> Result { - self.inner.write(buf).map_err(to_io_error) + self.inner.write(buf).map_err(to_std_error) } fn flush(&mut self) -> Result<(), std::io::Error> { - self.inner.flush().map_err(to_io_error) + self.inner.flush().map_err(to_std_error) } } impl std::io::Seek for ToStd { fn seek(&mut self, pos: std::io::SeekFrom) -> Result { - self.inner.seek(pos.into()).map_err(to_io_error) + self.inner.seek(pos.into()).map_err(to_std_error) } } -fn to_io_error(err: T) -> std::io::Error { +fn to_std_error(err: T) -> std::io::Error { let kind = std::io::ErrorKind::Other; std::io::Error::new(kind, format!("{:?}", err)) } diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index 7f7aa2eb9..40c98d0b2 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -281,7 +281,7 @@ pub trait ReadReady: crate::Io { /// This allows using a [`Write`] in a nonblocking fashion, i.e. trying to write /// only when it is ready. pub trait WriteReady: crate::Io { - /// Get whether the writer is ready for immediately writeing. + /// Get whether the writer is ready for immediately writing. /// /// This usually means that there is free space in the internal transmit buffer. /// From 7aaa585c634fb04ef5c12a8e190c1b728ef34db8 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 12 Jul 2023 13:26:23 +0200 Subject: [PATCH 12/12] Rename trait `Io` to `ErrorType`. --- embedded-io-async/src/lib.rs | 10 +++++----- embedded-io/CHANGELOG.md | 1 + embedded-io/src/adapters.rs | 2 +- embedded-io/src/impls/boxx.rs | 4 ++-- embedded-io/src/impls/slice_mut.rs | 4 ++-- embedded-io/src/impls/slice_ref.rs | 4 ++-- embedded-io/src/impls/vec.rs | 4 ++-- embedded-io/src/lib.rs | 18 +++++++++--------- 8 files changed, 24 insertions(+), 23 deletions(-) diff --git a/embedded-io-async/src/lib.rs b/embedded-io-async/src/lib.rs index f204eada5..d5a934ae2 100644 --- a/embedded-io-async/src/lib.rs +++ b/embedded-io-async/src/lib.rs @@ -10,13 +10,13 @@ extern crate alloc; mod impls; pub use embedded_io::{ - Error, ErrorKind, Io, ReadExactError, ReadReady, SeekFrom, WriteAllError, WriteReady, + Error, ErrorKind, ErrorType, ReadExactError, ReadReady, SeekFrom, WriteAllError, WriteReady, }; /// Async reader. /// /// Semantics are the same as [`std::io::Read`], check its documentation for details. -pub trait Read: Io { +pub trait Read: ErrorType { /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. async fn read(&mut self, buf: &mut [u8]) -> Result; @@ -40,7 +40,7 @@ pub trait Read: Io { /// Async buffered reader. /// /// Semantics are the same as [`std::io::BufRead`], check its documentation for details. -pub trait BufRead: Io { +pub trait BufRead: ErrorType { /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. async fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; @@ -51,7 +51,7 @@ pub trait BufRead: Io { /// Async writer. /// /// Semantics are the same as [`std::io::Write`], check its documentation for details. -pub trait Write: Io { +pub trait Write: ErrorType { /// Write a buffer into this writer, returning how many bytes were written. async fn write(&mut self, buf: &[u8]) -> Result; @@ -77,7 +77,7 @@ pub trait Write: Io { /// Async seek within streams. /// /// Semantics are the same as [`std::io::Seek`], check its documentation for details. -pub trait Seek: Io { +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/CHANGELOG.md b/embedded-io/CHANGELOG.md index f7043a7de..a91aabe37 100644 --- a/embedded-io/CHANGELOG.md +++ b/embedded-io/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved `embedded_io::blocking` to the crate root. - Split async traits to the `embedded-io-async` crate. - Split async trait adapters to separate crates. +- Rename trait `Io` to `ErrorKind`, for consistency with `embedded-hal`. ## 0.4.0 - 2022-11-25 diff --git a/embedded-io/src/adapters.rs b/embedded-io/src/adapters.rs index 741e243d0..729f8dc0d 100644 --- a/embedded-io/src/adapters.rs +++ b/embedded-io/src/adapters.rs @@ -40,7 +40,7 @@ impl FromStd { } } -impl crate::Io for FromStd { +impl crate::ErrorType for FromStd { type Error = std::io::Error; } diff --git a/embedded-io/src/impls/boxx.rs b/embedded-io/src/impls/boxx.rs index 727d1f21c..6e61ebf8d 100644 --- a/embedded-io/src/impls/boxx.rs +++ b/embedded-io/src/impls/boxx.rs @@ -1,8 +1,8 @@ -use crate::{BufRead, Io, Read, ReadReady, Seek, Write, WriteReady}; +use crate::{BufRead, ErrorType, Read, ReadReady, Seek, Write, WriteReady}; use alloc::boxed::Box; #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] -impl Io for Box { +impl ErrorType for Box { type Error = T::Error; } diff --git a/embedded-io/src/impls/slice_mut.rs b/embedded-io/src/impls/slice_mut.rs index cefaa296c..b89b72182 100644 --- a/embedded-io/src/impls/slice_mut.rs +++ b/embedded-io/src/impls/slice_mut.rs @@ -1,7 +1,7 @@ -use crate::{Io, Write}; +use crate::{ErrorType, Write}; use core::mem; -impl Io for &mut [u8] { +impl ErrorType for &mut [u8] { type Error = core::convert::Infallible; } diff --git a/embedded-io/src/impls/slice_ref.rs b/embedded-io/src/impls/slice_ref.rs index 73c98ab55..6332d70dd 100644 --- a/embedded-io/src/impls/slice_ref.rs +++ b/embedded-io/src/impls/slice_ref.rs @@ -1,6 +1,6 @@ -use crate::{BufRead, Io, Read}; +use crate::{BufRead, ErrorType, Read}; -impl Io for &[u8] { +impl ErrorType for &[u8] { type Error = core::convert::Infallible; } diff --git a/embedded-io/src/impls/vec.rs b/embedded-io/src/impls/vec.rs index 8aa1b62d6..3b279c564 100644 --- a/embedded-io/src/impls/vec.rs +++ b/embedded-io/src/impls/vec.rs @@ -1,8 +1,8 @@ -use crate::{Io, Write}; +use crate::{ErrorType, Write}; use alloc::vec::Vec; #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] -impl Io for Vec { +impl ErrorType for Vec { type Error = core::convert::Infallible; } diff --git a/embedded-io/src/lib.rs b/embedded-io/src/lib.rs index 40c98d0b2..b71c43f5a 100644 --- a/embedded-io/src/lib.rs +++ b/embedded-io/src/lib.rs @@ -55,7 +55,7 @@ impl Error for ErrorKind { } } -/// Base trait for all IO traits. +/// Base trait for all IO traits, defining the error type. /// /// All IO operations of all traits return the error defined in this trait. /// @@ -64,12 +64,12 @@ impl Error for ErrorKind { /// This is very convenient when writing generic code, it means you have to /// handle a single error type `T::Error`, instead of `::Error` and `::Error` /// which might be different types. -pub trait Io { +pub trait ErrorType { /// Error type of all the IO operations on this type. type Error: Error; } -impl crate::Io for &mut T { +impl crate::ErrorType for &mut T { type Error = T::Error; } @@ -150,7 +150,7 @@ impl std::error::Error for WriteAllError {} /// Blocking reader. /// /// Semantics are the same as [`std::io::Read`], check its documentation for details. -pub trait Read: crate::Io { +pub trait Read: crate::ErrorType { /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. fn read(&mut self, buf: &mut [u8]) -> Result; @@ -174,7 +174,7 @@ pub trait Read: crate::Io { /// Blocking buffered reader. /// /// Semantics are the same as [`std::io::BufRead`], check its documentation for details. -pub trait BufRead: crate::Io { +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. fn fill_buf(&mut self) -> Result<&[u8], Self::Error>; @@ -185,7 +185,7 @@ pub trait BufRead: crate::Io { /// Blocking writer. /// /// Semantics are the same as [`std::io::Write`], check its documentation for details. -pub trait Write: crate::Io { +pub trait Write: crate::ErrorType { /// Write a buffer into this writer, returning how many bytes were written. fn write(&mut self, buf: &[u8]) -> Result; @@ -246,7 +246,7 @@ pub trait Write: crate::Io { /// Blocking seek within streams. /// /// Semantics are the same as [`std::io::Seek`], check its documentation for details. -pub trait Seek: crate::Io { +pub trait Seek: crate::ErrorType { /// Seek to an offset, in bytes, in a stream. fn seek(&mut self, pos: crate::SeekFrom) -> Result; @@ -266,7 +266,7 @@ pub trait Seek: crate::Io { /// /// This allows using a [`Read`] or [`BufRead`] in a nonblocking fashion, i.e. trying to read /// only when it is ready. -pub trait ReadReady: crate::Io { +pub trait ReadReady: crate::ErrorType { /// Get whether the reader is ready for immediately reading. /// /// This usually means that there is either some bytes have been received and are buffered and ready to be read, @@ -280,7 +280,7 @@ pub trait ReadReady: crate::Io { /// /// This allows using a [`Write`] in a nonblocking fashion, i.e. trying to write /// only when it is ready. -pub trait WriteReady: crate::Io { +pub trait WriteReady: crate::ErrorType { /// Get whether the writer is ready for immediately writing. /// /// This usually means that there is free space in the internal transmit buffer.