diff --git a/io/Cargo.toml b/io/Cargo.toml index 292e5022c..f6747bb3b 100644 --- a/io/Cargo.toml +++ b/io/Cargo.toml @@ -23,4 +23,4 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [lints.rust] -unexpected_cfgs = { level = "deny" } +unexpected_cfgs = { level = "deny", check-cfg = ['cfg(rust_v_1_72)', 'cfg(rust_v_1_73)', 'cfg(rust_v_1_75)', 'cfg(rust_v_1_78)'] } diff --git a/io/build.rs b/io/build.rs new file mode 100644 index 000000000..2b741ca0d --- /dev/null +++ b/io/build.rs @@ -0,0 +1,37 @@ +fn main() { + let rustc = std::env::var_os("RUSTC"); + let rustc = rustc.as_ref().map(std::path::Path::new).unwrap_or_else(|| "rustc".as_ref()); + let output = std::process::Command::new(rustc) + .arg("--version") + .output() + .unwrap_or_else(|error| panic!("failed to run `{:?} --version`: {:?}", rustc, error)); + assert!(output.status.success(), "{:?} -- version returned non-zero exit code", rustc); + let stdout = String::from_utf8(output.stdout).expect("rustc produced non-UTF-8 output"); + let version_prefix = "rustc "; + if !stdout.starts_with(version_prefix) { + panic!("unexpected rustc output: {}", stdout); + } + + let version = &stdout[version_prefix.len()..]; + let end = version.find(&[' ', '-'] as &[_]).unwrap_or(version.len()); + let version = &version[..end]; + let mut version_components = version.split('.'); + let major = version_components.next().unwrap(); + assert_eq!(major, "1", "unexpected Rust major version"); + let minor = version_components + .next() + .unwrap_or("0") + .parse::() + .expect("invalid Rust minor version"); + + let msrv = std::env::var("CARGO_PKG_RUST_VERSION").unwrap(); + let mut msrv = msrv.split("."); + let msrv_major = msrv.next().unwrap(); + assert_eq!(msrv_major, "1", "unexpected Rust major version"); + let msrv_minor = msrv.next().unwrap().parse::().unwrap(); + + // print cfg for all interesting versions less than or equal to minor + for version in msrv_minor..=minor { + println!("cargo:rustc-cfg=rust_v_1_{}", version); + } +} diff --git a/io/src/bridge.rs b/io/src/bridge.rs new file mode 100644 index 000000000..85e999167 --- /dev/null +++ b/io/src/bridge.rs @@ -0,0 +1,541 @@ +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +/// A bridging wrapper providing the IO traits for types that already implement `std` IO traits. +#[repr(transparent)] +pub struct FromStd(T); + +impl FromStd { + /// Wraps an IO type. + #[inline] + pub const fn new(inner: T) -> Self { Self(inner) } + + /// Returns the wrapped value. + #[inline] + pub fn into_inner(self) -> T { + self.0 + } + + /// Returns a reference to the wrapped value. + #[inline] + pub fn inner(&self) -> &T { + &self.0 + } + + /// Returns a mutable reference to the wrapped value. + #[inline] + pub fn inner_mut(&mut self) -> &mut T { + &mut self.0 + } + + /// Wraps a mutable reference to IO type. + #[inline] + pub fn new_mut(inner: &mut T) -> &mut Self { + // SAFETY: the type is repr(transparent) and the lifetimes match + unsafe { &mut *(inner as *mut _ as *mut Self) } + } + + /// Wraps a boxed IO type. + #[cfg(feature = "alloc")] + #[inline] + pub fn new_boxed(inner: Box) -> Box { + // SAFETY: the type is repr(transparent) and the pointer is created from Box + unsafe { Box::from_raw(Box::into_raw(inner) as *mut Self) } + } +} + +impl super::Read for FromStd { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> super::Result { + self.0.read(buf).map_err(Into::into) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> super::Result<()> { + self.0.read_exact(buf).map_err(Into::into) + } +} + +impl super::BufRead for FromStd { + #[inline] + fn fill_buf(&mut self) -> super::Result<&[u8]> { + self.0.fill_buf().map_err(Into::into) + } + + #[inline] + fn consume(&mut self, amount: usize) { + self.0.consume(amount) + } +} + +impl super::Write for FromStd { + #[inline] + fn write(&mut self, buf: &[u8]) -> super::Result { + self.0.write(buf).map_err(Into::into) + } + + #[inline] + fn flush(&mut self) -> super::Result<()> { + self.0.flush().map_err(Into::into) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> super::Result<()> { + self.0.write_all(buf).map_err(Into::into) + } +} + +// We also impl std traits so that mixing the calls is not annoying. + +impl std::io::Read for FromStd { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.0.read(buf) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + self.0.read_exact(buf) + } +} + +impl std::io::BufRead for FromStd { + #[inline] + fn fill_buf(&mut self) -> std::io::Result<&[u8]> { + self.0.fill_buf() + } + + #[inline] + fn consume(&mut self, amount: usize) { + self.0.consume(amount) + } +} + +impl std::io::Write for FromStd { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write(buf) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { + self.0.write_all(buf) + } +} + +/// A bridging wrapper providing the std traits for types that already implement our traits. +#[repr(transparent)] +pub struct ToStd(T); + +impl ToStd { + /// Wraps an IO type. + #[inline] + pub const fn new(inner: T) -> Self { Self(inner) } + + /// Returns the wrapped value. + #[inline] + pub fn into_inner(self) -> T { + self.0 + } + + /// Returns a reference to the wrapped value. + #[inline] + pub fn inner(&self) -> &T { + &self.0 + } + + /// Returns a mutable reference to the wrapped value. + #[inline] + pub fn inner_mut(&mut self) -> &mut T { + &mut self.0 + } + + /// Wraps a mutable reference to IO type. + #[inline] + pub fn new_mut(inner: &mut T) -> &mut Self { + // SAFETY: the type is repr(transparent) and the lifetimes match + unsafe { &mut *(inner as *mut _ as *mut Self) } + } + + /// Wraps a boxed IO type. + #[cfg(feature = "alloc")] + #[inline] + pub fn new_boxed(inner: Box) -> Box { + // SAFETY: the type is repr(transparent) and the pointer is created from Box + unsafe { Box::from_raw(Box::into_raw(inner) as *mut Self) } + } +} + +impl std::io::Read for ToStd { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.0.read(buf).map_err(Into::into) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + self.0.read_exact(buf).map_err(Into::into) + } +} + +impl std::io::BufRead for ToStd { + #[inline] + fn fill_buf(&mut self) -> std::io::Result<&[u8]> { + self.0.fill_buf().map_err(Into::into) + } + + #[inline] + fn consume(&mut self, amount: usize) { + self.0.consume(amount) + } +} + +impl std::io::Write for ToStd { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.write(buf).map_err(Into::into) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + self.0.flush().map_err(Into::into) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { + self.0.write_all(buf).map_err(Into::into) + } +} + +// We also impl our traits so that mixing the calls is not annoying. + +impl super::Read for ToStd { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> super::Result { + self.0.read(buf) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> super::Result<()> { + self.0.read_exact(buf) + } +} + +impl super::BufRead for ToStd { + #[inline] + fn fill_buf(&mut self) -> super::Result<&[u8]> { + self.0.fill_buf() + } + + #[inline] + fn consume(&mut self, amount: usize) { + self.0.consume(amount) + } +} + +impl super::Write for ToStd { + #[inline] + fn write(&mut self, buf: &[u8]) -> super::Result { + self.0.write(buf) + } + + #[inline] + fn flush(&mut self) -> super::Result<()> { + self.0.flush() + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> super::Result<()> { + self.0.write_all(buf) + } +} + +macro_rules! impl_our { + (impl$(<$($gen:ident $(: $gent:path)?),*>)? Read for $std_type:ty $(where $($where:tt)*)?) => { + impl$(<$($gen$(: $gent)?),*>)? super::Read for $std_type $(where $($where)*)? { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> super::Result { + std::io::Read::read(self, buf).map_err(Into::into) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> super::Result<()> { + std::io::Read::read_exact(self, buf).map_err(Into::into) + } + } + }; + + (impl$(<$($gen:ident $(: $gent:path)?),*>)? BufRead for $std_type:ty $(where $($where:tt)*)?) => { + impl$(<$($gen$(: $gent)?),*>)? super::BufRead for $std_type $(where $($where)*)? { + #[inline] + fn fill_buf(&mut self) -> super::Result<&[u8]> { + std::io::BufRead::fill_buf(self).map_err(Into::into) + } + + #[inline] + fn consume(&mut self, amount: usize) { + std::io::BufRead::consume(self, amount) + } + } + }; + + (impl$(<$($gen:ident $(: $gent:path)?),*>)? Write for $std_type:ty $(where $($where:tt)*)?) => { + impl$(<$($gen$(: $gent)?),*>)? super::Write for $std_type $(where $($where)*)? { + #[inline] + fn write(&mut self, buf: &[u8]) -> super::Result { + std::io::Write::write(self, buf).map_err(Into::into) + } + + #[inline] + fn flush(&mut self) -> super::Result<()> { + std::io::Write::flush(self).map_err(Into::into) + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> super::Result<()> { + std::io::Write::write_all(self, buf).map_err(Into::into) + } + } + }; +} + +#[cfg(rust_v_1_72)] +impl_our! { + impl Read for std::io::BufReader where R: ?Sized +} + +#[cfg(not(rust_v_1_72))] +impl_our! { + impl Read for std::io::BufReader +} + +#[cfg(rust_v_1_72)] +impl_our! { + impl BufRead for std::io::BufReader where R: ?Sized +} + +#[cfg(not(rust_v_1_72))] +impl_our! { + impl BufRead for std::io::BufReader +} + +impl std::io::Write for super::Sink { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { Ok(buf.len()) } + + #[inline] + fn write_all(&mut self, _: &[u8]) -> std::io::Result<()> { Ok(()) } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { Ok(()) } +} + +#[cfg(rust_v_1_72)] +impl_our! { + impl Write for std::io::BufWriter where W: ?Sized +} + +#[cfg(not(rust_v_1_72))] +impl_our! { + impl Write for std::io::BufWriter +} + +#[cfg(rust_v_1_72)] +impl_our! { + impl Write for std::io::LineWriter where W: ?Sized +} + +#[cfg(not(rust_v_1_72))] +impl_our! { + impl Write for std::io::LineWriter +} + +impl_our! { + impl Read for std::io::Take +} + +impl_our! { + impl BufRead for std::io::Take +} + +impl_our! { + impl Read for std::io::Chain +} + +impl_our! { + impl BufRead for std::io::Chain +} + +impl_our! { + impl> Read for std::io::Cursor +} + +impl_our! { + impl> BufRead for std::io::Cursor +} + +impl_our! { + impl Write for std::io::Cursor> +} + +impl_our! { + impl Write for std::io::Cursor<&'_ mut std::vec::Vec> +} + +impl_our! { + impl Write for std::io::Cursor> +} + +impl_our! { + impl Read for std::io::Empty +} + +impl_our! { + impl BufRead for std::io::Empty +} + +#[cfg(rust_v_1_73)] +impl_our! { + impl Write for std::io::Empty +} + +// No idea why &Empty impls Write but not Read + BufRead +#[cfg(rust_v_1_73)] +impl_our! { + impl Write for &'_ std::io::Empty +} + +impl_our! { + impl Read for std::io::Repeat +} + +impl_our! { + impl Read for std::io::Stdin +} + +#[cfg(rust_v_1_78)] +impl_our! { + impl Read for &'_ std::io::Stdin +} + +impl_our! { + impl Write for std::io::Stdout +} + +impl_our! { + impl Write for &'_ std::io::Stdout +} + +impl_our! { + impl Write for std::io::Stderr +} + +impl_our! { + impl Write for &'_ std::io::Stderr +} + +impl_our! { + impl Read for std::io::StdinLock<'_> +} + +impl_our! { + impl BufRead for std::io::StdinLock<'_> +} + +impl_our! { + impl Read for std::fs::File +} + +impl_our! { + impl Write for std::fs::File +} + +impl_our! { + impl Read for &'_ std::fs::File +} + +impl_our! { + impl Write for &'_ std::fs::File +} + +#[cfg(rust_v_1_73)] +impl_our! { + impl Read for std::sync::Arc +} + +#[cfg(rust_v_1_73)] +impl_our! { + impl Write for std::sync::Arc +} + +impl_our! { + impl Read for std::net::TcpStream +} + +impl_our! { + impl Write for std::net::TcpStream +} + +impl_our! { + impl Read for &'_ std::net::TcpStream +} + +impl_our! { + impl Write for &'_ std::net::TcpStream +} + +#[cfg(target_family = "unix")] +impl_our! { + impl Read for std::os::unix::net::UnixStream +} + +#[cfg(target_family = "unix")] +impl_our! { + impl Write for std::os::unix::net::UnixStream +} + +#[cfg(target_family = "unix")] +impl_our! { + impl Read for &'_ std::os::unix::net::UnixStream +} + +#[cfg(target_family = "unix")] +impl_our! { + impl Write for &'_ std::os::unix::net::UnixStream +} + +impl_our! { + impl Read for std::process::ChildStderr +} + +impl_our! { + impl Read for std::process::ChildStdout +} + +impl_our! { + impl Write for std::process::ChildStdin +} + +// No ide why other &ChildStd* are not implemented +impl_our! { + impl Write for &'_ std::process::ChildStdin +} + +#[cfg(rust_v_1_75)] +impl_our! { + impl Read for std::collections::VecDeque +} + +#[cfg(rust_v_1_75)] +impl_our! { + impl BufRead for std::collections::VecDeque +} + +impl_our! { + impl Write for std::collections::VecDeque +} diff --git a/io/src/lib.rs b/io/src/lib.rs index e7878625e..1fc5dd53e 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -23,6 +23,11 @@ extern crate alloc; mod error; mod macros; +#[cfg(feature = "std")] +mod bridge; + +#[cfg(feature = "std")] +pub use bridge::{FromStd, ToStd}; #[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::vec::Vec; @@ -158,12 +163,6 @@ impl Read for &[u8] { } } -#[cfg(feature = "std")] -impl Read for std::io::BufReader { - #[inline] - fn read(&mut self, buf: &mut [u8]) -> Result { Ok(std::io::Read::read(self, buf)?) } -} - impl BufRead for &[u8] { #[inline] fn fill_buf(&mut self) -> Result<&[u8]> { Ok(self) } @@ -173,15 +172,6 @@ impl BufRead for &[u8] { fn consume(&mut self, amount: usize) { *self = &self[amount..] } } -#[cfg(feature = "std")] -impl BufRead for std::io::BufReader { - #[inline] - fn fill_buf(&mut self) -> Result<&[u8]> { Ok(std::io::BufRead::fill_buf(self)?) } - - #[inline] - fn consume(&mut self, amount: usize) { std::io::BufRead::consume(self, amount) } -} - /// Wraps an in memory reader providing the `position` function. pub struct Cursor { inner: T, @@ -197,11 +187,28 @@ impl> Cursor { #[inline] pub fn position(&self) -> u64 { self.pos } + /// Sets the internal position. + /// + /// This method allows seeking within the wrapped memory by setting the position. + /// + /// Note that setting a position that is larger than the buffer length will cause reads to + /// return no bytes (EOF). + #[inline] + pub fn set_position(&mut self, position: u64) { + self.pos = position; + } + /// Returns the inner buffer. /// /// This is the whole wrapped buffer, including the bytes already read. #[inline] pub fn into_inner(self) -> T { self.inner } + + /// Returns a reference to the inner buffer. + /// + /// This is the whole wrapped buffer, including the bytes already read. + #[inline] + pub fn inner(&self) -> &T { &self.inner } } impl> Read for Cursor { @@ -279,15 +286,6 @@ impl<'a> Write for &'a mut [u8] { fn flush(&mut self) -> Result<()> { Ok(()) } } -#[cfg(feature = "std")] -impl Write for std::io::BufWriter { - #[inline] - fn write(&mut self, buf: &[u8]) -> Result { Ok(std::io::Write::write(self, buf)?) } - - #[inline] - fn flush(&mut self) -> Result<()> { Ok(std::io::Write::flush(self)?) } -} - /// A sink to which all writes succeed. See [`std::io::Sink`] for more info. /// /// Created using `io::sink()`. @@ -304,22 +302,28 @@ impl Write for Sink { fn flush(&mut self) -> Result<()> { Ok(()) } } -#[cfg(feature = "std")] -impl std::io::Write for Sink { - #[inline] - fn write(&mut self, buf: &[u8]) -> std::io::Result { Ok(buf.len()) } - - #[inline] - fn write_all(&mut self, _: &[u8]) -> std::io::Result<()> { Ok(()) } - - #[inline] - fn flush(&mut self) -> std::io::Result<()> { Ok(()) } -} - /// Returns a sink to which all writes succeed. See [`std::io::sink`] for more info. #[inline] pub fn sink() -> Sink { Sink } +/// Wraps a `std` IO type to implement the traits from this crate. +/// +/// All methods are passed through converting the errors. +#[cfg(feature = "std")] +#[inline] +pub const fn from_std(std_io: T) -> FromStd { + FromStd::new(std_io) +} + +/// Wraps a mutable reference to `std` IO type to implement the traits from this crate. +/// +/// All methods are passed through converting the errors. +#[cfg(feature = "std")] +#[inline] +pub fn from_std_mut(std_io: &mut T) -> &mut FromStd { + FromStd::new_mut(std_io) +} + #[cfg(test)] mod tests { #[cfg(all(not(feature = "std"), feature = "alloc"))]