Skip to content

Commit b4dfac9

Browse files
authored
Merge pull request #505 from rust-embedded/write-ok-0
Clarify write() may only return Ok(0) if data is empty
2 parents 030a232 + 9d40f6b commit b4dfac9

File tree

8 files changed

+91
-68
lines changed

8 files changed

+91
-68
lines changed

embedded-io-adapters/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## Unreleased
99

1010
- Add support for adapting `BufRead` from `futures` and `tokio`.
11+
- Return an error when a wrapped `std`/`futures`/`tokio` `write()` call returns
12+
`Ok(0)` to comply with `embedded_io::Write` requirements.
1113

1214
## 0.5.0 - 2023-08-06
1315

14-
- First release
16+
- First release

embedded-io-adapters/src/futures_03.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ impl<T: futures::io::AsyncBufRead + Unpin + ?Sized> embedded_io_async::BufRead f
5757

5858
impl<T: futures::io::AsyncWrite + Unpin + ?Sized> embedded_io_async::Write for FromFutures<T> {
5959
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
60-
poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await
60+
match poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await {
61+
Ok(0) if !buf.is_empty() => Err(std::io::ErrorKind::WriteZero.into()),
62+
Ok(n) => Ok(n),
63+
Err(e) => Err(e),
64+
}
6165
}
6266

6367
async fn flush(&mut self) -> Result<(), Self::Error> {

embedded-io-adapters/src/std.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Adapters to/from `std::io` traits.
22
3+
use embedded_io::Error;
4+
35
/// Adapter from `std::io` traits.
46
#[derive(Clone)]
57
pub struct FromStd<T: ?Sized> {
@@ -52,7 +54,11 @@ impl<T: std::io::BufRead + ?Sized> embedded_io::BufRead for FromStd<T> {
5254

5355
impl<T: std::io::Write + ?Sized> embedded_io::Write for FromStd<T> {
5456
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
55-
self.inner.write(buf)
57+
match self.inner.write(buf) {
58+
Ok(0) if !buf.is_empty() => Err(std::io::ErrorKind::WriteZero.into()),
59+
Ok(n) => Ok(n),
60+
Err(e) => Err(e),
61+
}
5662
}
5763
fn flush(&mut self) -> Result<(), Self::Error> {
5864
self.inner.flush()
@@ -103,7 +109,11 @@ impl<T: embedded_io::Read + ?Sized> std::io::Read for ToStd<T> {
103109

104110
impl<T: embedded_io::Write + ?Sized> std::io::Write for ToStd<T> {
105111
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
106-
self.inner.write(buf).map_err(to_std_error)
112+
match self.inner.write(buf) {
113+
Ok(n) => Ok(n),
114+
Err(e) if e.kind() == embedded_io::ErrorKind::WriteZero => Ok(0),
115+
Err(e) => Err(to_std_error(e)),
116+
}
107117
}
108118
fn flush(&mut self) -> Result<(), std::io::Error> {
109119
self.inner.flush().map_err(to_std_error)

embedded-io-adapters/src/tokio_1.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ impl<T: tokio::io::AsyncBufRead + Unpin + ?Sized> embedded_io_async::BufRead for
6868

6969
impl<T: tokio::io::AsyncWrite + Unpin + ?Sized> embedded_io_async::Write for FromTokio<T> {
7070
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
71-
poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await
71+
match poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await {
72+
Ok(0) if !buf.is_empty() => Err(std::io::ErrorKind::WriteZero.into()),
73+
Ok(n) => Ok(n),
74+
Err(e) => Err(e),
75+
}
7276
}
7377

7478
async fn flush(&mut self) -> Result<(), Self::Error> {

embedded-io-async/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ extern crate alloc;
1010
mod impls;
1111

1212
pub use embedded_io::{
13-
Error, ErrorKind, ErrorType, ReadExactError, ReadReady, SeekFrom, WriteAllError, WriteReady,
13+
Error, ErrorKind, ErrorType, ReadExactError, ReadReady, SeekFrom, WriteReady,
1414
};
1515

1616
/// Async reader.
@@ -125,13 +125,13 @@ pub trait Write: ErrorType {
125125
///
126126
/// This function is not side-effect-free on cancel (AKA "cancel-safe"), i.e. if you cancel (drop) a returned
127127
/// future that hasn't completed yet, some bytes might have already been written.
128-
async fn write_all(&mut self, buf: &[u8]) -> Result<(), WriteAllError<Self::Error>> {
128+
async fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
129129
let mut buf = buf;
130130
while !buf.is_empty() {
131131
match self.write(buf).await {
132-
Ok(0) => return Err(WriteAllError::WriteZero),
132+
Ok(0) => panic!("write() returned Ok(0)"),
133133
Ok(n) => buf = &buf[n..],
134-
Err(e) => return Err(WriteAllError::Other(e)),
134+
Err(e) => return Err(e),
135135
}
136136
}
137137
Ok(())

embedded-io/CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
- Prohibit `Write::write` implementations returning `Ok(0)` unless there is no
11+
data to write; consequently remove `WriteAllError` and the `WriteAllError`
12+
variant of `WriteFmtError`. Update the `&mut [u8]` impl to possibly return
13+
a new `SliceWriteError` if the slice is full instead of `Ok(0)`.
14+
- Add `WriteZero` variant to `ErrorKind` for implementations that previously
15+
may have returned `Ok(0)` to indicate no further data could be written.
16+
- `Write::write_all` now panics if the `write()` implementation returns `Ok(0)`.
17+
818
## 0.5.0 - 2023-08-06
919

1020
- Add `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.
@@ -37,4 +47,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3747

3848
## 0.2.0 - 2022-05-07
3949

40-
- First release
50+
- First release

embedded-io/src/impls/slice_mut.rs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,56 @@
1-
use crate::{ErrorType, Write};
1+
use crate::{Error, ErrorKind, ErrorType, Write};
22
use core::mem;
33

4+
// needed to prevent defmt macros from breaking, since they emit code that does `defmt::blahblah`.
5+
#[cfg(feature = "defmt-03")]
6+
use defmt_03 as defmt;
7+
8+
/// Errors that could be returned by `Write` on `&mut [u8]`.
9+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
10+
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
11+
#[non_exhaustive]
12+
pub enum SliceWriteError {
13+
/// The target slice was full and so could not receive any new data.
14+
Full,
15+
}
16+
17+
impl Error for SliceWriteError {
18+
fn kind(&self) -> ErrorKind {
19+
match self {
20+
SliceWriteError::Full => ErrorKind::WriteZero,
21+
}
22+
}
23+
}
24+
425
impl ErrorType for &mut [u8] {
5-
type Error = core::convert::Infallible;
26+
type Error = SliceWriteError;
627
}
728

29+
impl core::fmt::Display for SliceWriteError {
30+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31+
write!(f, "{self:?}")
32+
}
33+
}
34+
35+
#[cfg(feature = "std")]
36+
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
37+
impl std::error::Error for SliceWriteError {}
38+
839
/// Write is implemented for `&mut [u8]` by copying into the slice, overwriting
940
/// its data.
1041
///
1142
/// Note that writing updates the slice to point to the yet unwritten part.
1243
/// The slice will be empty when it has been completely overwritten.
1344
///
1445
/// If the number of bytes to be written exceeds the size of the slice, write operations will
15-
/// return short writes: ultimately, `Ok(0)`; in this situation, `write_all` returns an error of
16-
/// kind `ErrorKind::WriteZero`.
46+
/// return short writes: ultimately, a `SliceWriteError::Full`.
1747
impl Write for &mut [u8] {
1848
#[inline]
1949
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
2050
let amt = core::cmp::min(buf.len(), self.len());
51+
if !buf.is_empty() && amt == 0 {
52+
return Err(SliceWriteError::Full);
53+
}
2154
let (a, b) = mem::take(self).split_at_mut(amt);
2255
a.copy_from_slice(&buf[..amt]);
2356
*self = b;

embedded-io/src/lib.rs

Lines changed: 14 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ impl From<std::io::SeekFrom> for SeekFrom {
6161
/// This is the `embedded-io` equivalent of [`std::io::ErrorKind`], except with the following changes:
6262
///
6363
/// - `WouldBlock` is removed, since `embedded-io` traits are always blocking. See the [crate-level documentation](crate) for details.
64-
/// - `WriteZero` is removed, since it is a separate variant in [`WriteAllError`] and [`WriteFmtError`].
6564
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
6665
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
6766
#[non_exhaustive]
@@ -117,6 +116,8 @@ pub enum ErrorKind {
117116
/// An operation could not be completed, because it failed
118117
/// to allocate enough memory.
119118
OutOfMemory,
119+
/// An attempted write could not write any data.
120+
WriteZero,
120121
}
121122

122123
#[cfg(feature = "std")]
@@ -248,19 +249,6 @@ impl From<ReadExactError<std::io::Error>> for std::io::Error {
248249
}
249250
}
250251

251-
#[cfg(feature = "std")]
252-
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
253-
impl From<WriteAllError<std::io::Error>> for std::io::Error {
254-
fn from(err: WriteAllError<std::io::Error>) -> Self {
255-
match err {
256-
WriteAllError::WriteZero => {
257-
std::io::Error::new(std::io::ErrorKind::WriteZero, "WriteZero".to_owned())
258-
}
259-
WriteAllError::Other(e) => std::io::Error::new(e.kind(), format!("{e:?}")),
260-
}
261-
}
262-
}
263-
264252
impl<E: fmt::Debug> fmt::Display for ReadExactError<E> {
265253
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266254
write!(f, "{self:?}")
@@ -275,8 +263,6 @@ impl<E: fmt::Debug> std::error::Error for ReadExactError<E> {}
275263
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
276264
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
277265
pub enum WriteFmtError<E> {
278-
/// [`Write::write`] wrote zero bytes
279-
WriteZero,
280266
/// An error was encountered while formatting.
281267
FmtError,
282268
/// Error returned by the inner Write.
@@ -299,32 +285,6 @@ impl<E: fmt::Debug> fmt::Display for WriteFmtError<E> {
299285
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
300286
impl<E: fmt::Debug> std::error::Error for WriteFmtError<E> {}
301287

302-
/// Error returned by [`Write::write_all`]
303-
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
304-
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
305-
pub enum WriteAllError<E> {
306-
/// [`Write::write`] wrote zero bytes
307-
WriteZero,
308-
/// Error returned by the inner Write.
309-
Other(E),
310-
}
311-
312-
impl<E> From<E> for WriteAllError<E> {
313-
fn from(err: E) -> Self {
314-
Self::Other(err)
315-
}
316-
}
317-
318-
impl<E: fmt::Debug> fmt::Display for WriteAllError<E> {
319-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320-
write!(f, "{self:?}")
321-
}
322-
}
323-
324-
#[cfg(feature = "std")]
325-
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
326-
impl<E: fmt::Debug> std::error::Error for WriteAllError<E> {}
327-
328288
/// Blocking reader.
329289
///
330290
/// This trait is the `embedded-io` equivalent of [`std::io::Read`].
@@ -401,11 +361,12 @@ pub trait Write: ErrorType {
401361
/// implementation to write an amount of bytes less than `buf.len()` while the writer continues to be
402362
/// ready to accept more bytes immediately.
403363
///
404-
/// Implementations should never return `Ok(0)` when `buf.len() != 0`. Situations where the writer is not
405-
/// able to accept more bytes and likely never will are better indicated with errors.
364+
/// Implementations must not return `Ok(0)` unless `buf` is empty. Situations where the
365+
/// writer is not able to accept more bytes must instead be indicated with an error,
366+
/// where the `ErrorKind` is `WriteZero`.
406367
///
407-
/// If `buf.len() == 0`, `write` returns without blocking, with either `Ok(0)` or an error.
408-
/// The `Ok(0)` doesn't indicate an error.
368+
/// If `buf` is empty, `write` returns without blocking, with either `Ok(0)` or an error.
369+
/// `Ok(0)` doesn't indicate an error.
409370
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error>;
410371

411372
/// Flush this output stream, blocking until all intermediately buffered contents reach their destination.
@@ -419,12 +380,14 @@ pub trait Write: ErrorType {
419380
/// If you are using [`WriteReady`] to avoid blocking, you should not use this function.
420381
/// `WriteReady::write_ready()` returning true only guarantees the first call to `write()` will
421382
/// not block, so this function may still block in subsequent calls.
422-
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), WriteAllError<Self::Error>> {
383+
///
384+
/// This function will panic if `write()` returns `Ok(0)`.
385+
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Self::Error> {
423386
while !buf.is_empty() {
424387
match self.write(buf) {
425-
Ok(0) => return Err(WriteAllError::WriteZero),
388+
Ok(0) => panic!("write() returned Ok(0)"),
426389
Ok(n) => buf = &buf[n..],
427-
Err(e) => return Err(WriteAllError::Other(e)),
390+
Err(e) => return Err(e),
428391
}
429392
}
430393
Ok(())
@@ -443,7 +406,7 @@ pub trait Write: ErrorType {
443406
// off I/O errors. instead of discarding them
444407
struct Adapter<'a, T: Write + ?Sized + 'a> {
445408
inner: &'a mut T,
446-
error: Result<(), WriteAllError<T::Error>>,
409+
error: Result<(), T::Error>,
447410
}
448411

449412
impl<T: Write + ?Sized> fmt::Write for Adapter<'_, T> {
@@ -466,10 +429,7 @@ pub trait Write: ErrorType {
466429
Ok(()) => Ok(()),
467430
Err(..) => match output.error {
468431
// check if the error came from the underlying `Write` or not
469-
Err(e) => match e {
470-
WriteAllError::WriteZero => Err(WriteFmtError::WriteZero),
471-
WriteAllError::Other(e) => Err(WriteFmtError::Other(e)),
472-
},
432+
Err(e) => Err(WriteFmtError::Other(e)),
473433
Ok(()) => Err(WriteFmtError::FmtError),
474434
},
475435
}

0 commit comments

Comments
 (0)