Merge rust-bitcoin/rust-bitcoin#3176: Io improvements

56b19d0601 Add missing IO impls for `std` types (Martin Habovstiak)
5e30c9f190 Use macro to implement our traits for `std` types (Martin Habovstiak)
505ecd8a2e Move `std` impl from `lib.rs` to `bridge.rs` (Martin Habovstiak)
94768d3f70 Add `set_position` method to `Cursor` (Martin Habovstiak)
fc7e213f21 Add `bitcoin-io` -> `std` bridge (Martin Habovstiak)
54fdcb798b Add `std` -> `bitcoin-io` bridge (Martin Habovstiak)

Pull request description:

  This addresses significant API holes. Originally I wanted to add a commit that bumps the IO version but then I remembered we have #3162 which we most likely also want to merge and release.

  Closes #3174
  Closes #3175

ACKs for top commit:
  apoelstra:
    ACK 56b19d0601 successfully ran local tests
  tcharding:
    ACK 56b19d0601

Tree-SHA512: 9b0934da8dfd962c916c74032d734583175c6016f514861e24a29179c15925389a506ccf1b784a1bf99bc22909b5e7dcaece5fb689bd32f21ca0112ee798eca5
This commit is contained in:
merge-script 2024-08-17 16:54:52 +00:00
commit c00faa0458
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
4 changed files with 619 additions and 37 deletions

View File

@ -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)'] }

37
io/build.rs Normal file
View File

@ -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::<u64>()
.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::<u64>().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);
}
}

541
io/src/bridge.rs Normal file
View File

@ -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>(T);
impl<T> FromStd<T> {
/// 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<T>) -> Box<Self> {
// 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<T: std::io::Read> super::Read for FromStd<T> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> super::Result<usize> {
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<T: std::io::BufRead> super::BufRead for FromStd<T> {
#[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<T: std::io::Write> super::Write for FromStd<T> {
#[inline]
fn write(&mut self, buf: &[u8]) -> super::Result<usize> {
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<T: std::io::Read> std::io::Read for FromStd<T> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.0.read(buf)
}
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
self.0.read_exact(buf)
}
}
impl<T: std::io::BufRead> std::io::BufRead for FromStd<T> {
#[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<T: std::io::Write> std::io::Write for FromStd<T> {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
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>(T);
impl<T> ToStd<T> {
/// 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<T>) -> Box<Self> {
// 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<T: super::Read> std::io::Read for ToStd<T> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
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<T: super::BufRead> std::io::BufRead for ToStd<T> {
#[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<T: super::Write> std::io::Write for ToStd<T> {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
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<T: super::Read> super::Read for ToStd<T> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> super::Result<usize> {
self.0.read(buf)
}
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> super::Result<()> {
self.0.read_exact(buf)
}
}
impl<T: super::BufRead> super::BufRead for ToStd<T> {
#[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<T: super::Write> super::Write for ToStd<T> {
#[inline]
fn write(&mut self, buf: &[u8]) -> super::Result<usize> {
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<usize> {
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<usize> {
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<R: std::io::Read> Read for std::io::BufReader<R> where R: ?Sized
}
#[cfg(not(rust_v_1_72))]
impl_our! {
impl<R: std::io::Read> Read for std::io::BufReader<R>
}
#[cfg(rust_v_1_72)]
impl_our! {
impl<R: std::io::Read> BufRead for std::io::BufReader<R> where R: ?Sized
}
#[cfg(not(rust_v_1_72))]
impl_our! {
impl<R: std::io::Read> BufRead for std::io::BufReader<R>
}
impl std::io::Write for super::Sink {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { 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<W: std::io::Write> Write for std::io::BufWriter<W> where W: ?Sized
}
#[cfg(not(rust_v_1_72))]
impl_our! {
impl<W: std::io::Write> Write for std::io::BufWriter<W>
}
#[cfg(rust_v_1_72)]
impl_our! {
impl<W: std::io::Write> Write for std::io::LineWriter<W> where W: ?Sized
}
#[cfg(not(rust_v_1_72))]
impl_our! {
impl<W: std::io::Write> Write for std::io::LineWriter<W>
}
impl_our! {
impl<R: std::io::Read> Read for std::io::Take<R>
}
impl_our! {
impl<R: std::io::BufRead> BufRead for std::io::Take<R>
}
impl_our! {
impl<R1: std::io::Read, R2: std::io::Read> Read for std::io::Chain<R1, R2>
}
impl_our! {
impl<R1: std::io::BufRead, R2: std::io::BufRead> BufRead for std::io::Chain<R1, R2>
}
impl_our! {
impl<T: AsRef<[u8]>> Read for std::io::Cursor<T>
}
impl_our! {
impl<T: AsRef<[u8]>> BufRead for std::io::Cursor<T>
}
impl_our! {
impl Write for std::io::Cursor<std::vec::Vec<u8>>
}
impl_our! {
impl Write for std::io::Cursor<&'_ mut std::vec::Vec<u8>>
}
impl_our! {
impl Write for std::io::Cursor<std::boxed::Box<[u8]>>
}
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<std::fs::File>
}
#[cfg(rust_v_1_73)]
impl_our! {
impl Write for std::sync::Arc<std::fs::File>
}
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<u8>
}
#[cfg(rust_v_1_75)]
impl_our! {
impl BufRead for std::collections::VecDeque<u8>
}
impl_our! {
impl Write for std::collections::VecDeque<u8>
}

View File

@ -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<R: std::io::Read> Read for std::io::BufReader<R> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { 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<R: std::io::Read> BufRead for std::io::BufReader<R> {
#[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<T> {
inner: T,
@ -197,11 +187,28 @@ impl<T: AsRef<[u8]>> Cursor<T> {
#[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<T: AsRef<[u8]>> Read for Cursor<T> {
@ -279,15 +286,6 @@ impl<'a> Write for &'a mut [u8] {
fn flush(&mut self) -> Result<()> { Ok(()) }
}
#[cfg(feature = "std")]
impl<W: std::io::Write> Write for std::io::BufWriter<W> {
#[inline]
fn write(&mut self, buf: &[u8]) -> Result<usize> { 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<usize> { 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<T>(std_io: T) -> FromStd<T> {
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<T>(std_io: &mut T) -> &mut FromStd<T> {
FromStd::new_mut(std_io)
}
#[cfg(test)]
mod tests {
#[cfg(all(not(feature = "std"), feature = "alloc"))]