Skip to content

Commit af07c46

Browse files
committed
Export std::io instead of always using thin reimplementation
This allows most implementers of `BinRead` to use std features on the reader instead of restricting them to the subset of features exposed by the no_std reimplementation. This also appears to have been the original intent of the `binread::io` module, per the module-level comment. This is a breaking change because `iter_bytes` is called `bytes` in std and consumes the reader, so this has been changed in the binread implementation to conform to that API. This commit also fixes the earlier binread implementations not handling errors as std does; this is now fixed (by copying from std). (Hopefully rust-lang/rust#48331 will be addressed someday and then this code can disappear entirely.)
1 parent af63fd3 commit af07c46

File tree

7 files changed

+200
-83
lines changed

7 files changed

+200
-83
lines changed

binread/src/io/error.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ enum Repr {
1616
}
1717

1818
#[non_exhaustive]
19-
#[derive(Debug)]
19+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2020
pub enum ErrorKind {
2121
NotFound,
2222
PermissionDenied,
@@ -44,4 +44,10 @@ impl Error {
4444
repr: Repr::Simple(kind)
4545
}
4646
}
47+
48+
pub fn kind(&self) -> ErrorKind {
49+
match self.repr {
50+
Repr::Simple(kind) => kind,
51+
}
52+
}
4753
}

binread/src/io/mod.rs

Lines changed: 4 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,14 @@
11
//! A swappable version of [std::io](std::io) that works in `no_std + alloc` environments.
22
//! If the feature flag `std` is enabled (as it is by default), this will just re-export types from `std::io`.
3+
34
pub mod prelude;
45
pub mod cursor;
56
pub mod error;
67

7-
#[cfg(feature = "std")]
8-
pub use std::io::{Error, ErrorKind};
9-
10-
#[cfg(not(feature = "std"))]
11-
pub use error::{Error, ErrorKind};
12-
13-
#[cfg(feature = "std")]
14-
pub use std::io::Result;
15-
168
#[cfg(not(feature = "std"))]
17-
pub type Result<T> = core::result::Result<T, Error>;
18-
19-
/// A simplified version of [std::io::Read](std::io::Read) for use in no_std environments
20-
pub trait Read {
21-
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
22-
23-
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
24-
if let Ok(n) = self.read(buf) {
25-
if n == buf.len() {
26-
return Ok(())
27-
}
28-
}
29-
30-
Err(Error::new(ErrorKind::UnexpectedEof, "Out of bytes in reader"))
31-
}
32-
33-
fn iter_bytes(&mut self) -> Bytes<'_, Self>
34-
where Self: Sized,
35-
{
36-
Bytes {
37-
inner: self
38-
}
39-
}
40-
}
41-
42-
pub struct Bytes<'a, R: Read> {
43-
inner: &'a mut R
44-
}
45-
46-
impl<'a, R: Read> Iterator for Bytes<'a, R> {
47-
type Item = Result<u8>;
48-
49-
fn next(&mut self) -> Option<Self::Item> {
50-
let mut byte = [0u8];
51-
Some(
52-
self.inner.read_exact(&mut byte)
53-
.map(|_| byte[0])
54-
)
55-
}
56-
}
57-
58-
#[cfg(feature = "std")]
59-
pub use std::io::SeekFrom;
60-
9+
mod no_std;
6110
#[cfg(not(feature = "std"))]
62-
#[derive(Debug, Clone, Copy)]
63-
pub enum SeekFrom {
64-
Start(u64),
65-
End(i64),
66-
Current(i64),
67-
}
68-
69-
pub trait Seek {
70-
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
71-
}
11+
pub use no_std::*;
7212

7313
#[cfg(feature = "std")]
74-
impl<R: std::io::Read> Read for R {
75-
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
76-
self.read(buf)
77-
}
78-
}
79-
80-
#[cfg(feature = "std")]
81-
impl<S: std::io::Seek> Seek for S {
82-
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
83-
self.seek(pos)
84-
}
85-
}
86-
87-
#[cfg(feature = "std")]
88-
pub use std::io::Cursor;
89-
90-
#[cfg(not(feature = "std"))]
91-
pub use cursor::Cursor;
14+
pub use std::io::{Bytes, Cursor, Error, ErrorKind, Read, Result, Seek, SeekFrom};

binread/src/io/no_std.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
pub use super::{cursor::Cursor, error::{Error, ErrorKind}};
2+
3+
pub type Result<T> = core::result::Result<T, Error>;
4+
5+
/// A simplified version of [std::io::Read](std::io::Read) for use in no_std environments
6+
pub trait Read {
7+
fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
8+
9+
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
10+
while !buf.is_empty() {
11+
match self.read(buf) {
12+
Ok(0) => break,
13+
Ok(n) => {
14+
let tmp = buf;
15+
buf = &mut tmp[n..];
16+
}
17+
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
18+
Err(e) => return Err(e),
19+
}
20+
}
21+
if !buf.is_empty() {
22+
Err(Error::new(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
23+
} else {
24+
Ok(())
25+
}
26+
}
27+
28+
fn bytes(self) -> Bytes<Self>
29+
where
30+
Self: Sized,
31+
{
32+
Bytes { inner: self }
33+
}
34+
35+
fn by_ref(&mut self) -> &mut Self
36+
where
37+
Self: Sized,
38+
{
39+
self
40+
}
41+
}
42+
43+
impl<R: Read + ?Sized> Read for &mut R {
44+
#[inline]
45+
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
46+
(**self).read(buf)
47+
}
48+
49+
#[inline]
50+
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
51+
(**self).read_exact(buf)
52+
}
53+
}
54+
55+
#[derive(Debug)]
56+
pub struct Bytes<R: Read> {
57+
inner: R
58+
}
59+
60+
impl<R: Read> Iterator for Bytes<R> {
61+
type Item = Result<u8>;
62+
63+
fn next(&mut self) -> Option<Result<u8>> {
64+
let mut byte = 0;
65+
loop {
66+
return match self.inner.read(core::slice::from_mut(&mut byte)) {
67+
Ok(0) => None,
68+
Ok(..) => Some(Ok(byte)),
69+
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
70+
Err(e) => Some(Err(e)),
71+
};
72+
}
73+
}
74+
}
75+
76+
#[derive(Debug, Clone, Copy)]
77+
pub enum SeekFrom {
78+
Start(u64),
79+
End(i64),
80+
Current(i64),
81+
}
82+
83+
pub trait Seek {
84+
fn seek(&mut self, pos: SeekFrom) -> Result<u64>;
85+
}
86+
87+
impl<S: Seek + ?Sized> Seek for &mut S {
88+
#[inline]
89+
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
90+
(**self).seek(pos)
91+
}
92+
}

binread/src/strings.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl BinRead for Vec<NonZeroU8> {
2727
fn read_options<R: Read + Seek>(reader: &mut R, _: &ReadOptions, _: Self::Args) -> BinResult<Self>
2828
{
2929
reader
30-
.iter_bytes()
30+
.bytes()
3131
.take_while(|x| !matches!(x, Ok(0)))
3232
.map(|x| Ok(x.map(|byte| unsafe { NonZeroU8::new_unchecked(byte) })?))
3333
.collect()

binread/tests/io/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#[cfg(not(feature = "std"))]
2+
mod no_std;

binread/tests/io/no_std.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use binread::io::{Cursor, Error, ErrorKind, Read, Result};
2+
3+
#[derive(Debug)]
4+
struct MalfunctioningEddie<'data> {
5+
error: Option<Error>,
6+
data: Cursor<&'data [u8]>,
7+
}
8+
9+
impl <'data> MalfunctioningEddie<'data> {
10+
fn new(data: &'data [u8]) -> Self {
11+
Self {
12+
error: None,
13+
data: Cursor::new(data),
14+
}
15+
}
16+
17+
// Pleased to meet you!
18+
// > Actually, we’ve met once before.
19+
fn trigger_fatal_error(&mut self) {
20+
// WHAT?!
21+
self.error = Some(Error::new(ErrorKind::BrokenPipe, ""));
22+
}
23+
24+
// > You are being released.
25+
// Me? What a surprise!
26+
fn trigger_non_fatal_error(&mut self) {
27+
self.error = Some(Error::new(ErrorKind::Interrupted, ""));
28+
// Look! I barely exploded at all!
29+
}
30+
}
31+
32+
impl Read for MalfunctioningEddie<'_> {
33+
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
34+
if let Some(error) = self.error.take() {
35+
Err(error)
36+
} else {
37+
self.data.read(buf)
38+
}
39+
}
40+
}
41+
42+
#[test]
43+
fn bytes() {
44+
let mut cursor = MalfunctioningEddie::new(b"\0\x01\x02\x03\x04\x05");
45+
{
46+
let mut bytes = cursor.by_ref().bytes();
47+
assert!(matches!(bytes.next(), Some(Ok(0))));
48+
assert!(matches!(bytes.next(), Some(Ok(1))));
49+
}
50+
51+
// Interrupted error should cause a retry
52+
cursor.trigger_non_fatal_error();
53+
{
54+
let mut bytes = cursor.by_ref().bytes();
55+
assert!(matches!(bytes.next(), Some(Ok(2))));
56+
}
57+
58+
// Reads through Bytes should have advanced the underlying stream
59+
let mut raw_read_data = [0u8; 2];
60+
assert_eq!(cursor.read(&mut raw_read_data).unwrap(), 2);
61+
assert_eq!(raw_read_data, [3, 4]);
62+
63+
// Errors other than Interrupted should be returned
64+
cursor.trigger_fatal_error();
65+
let mut bytes = cursor.bytes();
66+
assert_eq!(bytes.next().unwrap().unwrap_err().kind(), ErrorKind::BrokenPipe);
67+
}
68+
69+
#[test]
70+
fn read_exact() {
71+
let mut cursor = MalfunctioningEddie::new(b"\0\x01\x02\x03\x04\x05");
72+
73+
let mut raw_read_data = [0u8; 2];
74+
cursor.read_exact(&mut raw_read_data).unwrap();
75+
assert_eq!(raw_read_data, [0, 1]);
76+
77+
// Interrupted error should cause a retry
78+
cursor.trigger_non_fatal_error();
79+
cursor.read_exact(&mut raw_read_data).unwrap();
80+
assert_eq!(raw_read_data, [2, 3]);
81+
82+
// Errors other than Interrupted should be returned
83+
cursor.trigger_fatal_error();
84+
assert_eq!(cursor.read_exact(&mut raw_read_data).unwrap_err().kind(), ErrorKind::BrokenPipe);
85+
86+
// Read through a mutable reference should work as if it were directly on
87+
// the cursor
88+
cursor.by_ref().read_exact(&mut raw_read_data).unwrap();
89+
assert_eq!(raw_read_data, [4, 5]);
90+
91+
// EOF reads should not succeed
92+
assert_eq!(cursor.read_exact(&mut raw_read_data).unwrap_err().kind(), ErrorKind::UnexpectedEof);
93+
}

binread/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod io;

0 commit comments

Comments
 (0)