From f6d8df82f2ab506601705849332b122fd6e29126 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 12 May 2026 14:10:14 +1000 Subject: [PATCH 01/11] Move `std::io::buffered` to `alloc::io` --- .../src/io/buffered/bufreader.rs | 75 ++++++- .../src/io/buffered/bufreader/buffer.rs | 13 +- .../src/io/buffered/bufwriter.rs | 33 ++- .../src/io/buffered/linewriter.rs | 2 + .../src/io/buffered/linewritershim.rs | 4 +- library/alloc/src/io/buffered/mod.rs | 189 ++++++++++++++++++ library/alloc/src/io/mod.rs | 2 + library/std/src/io/buffered/mod.rs | 185 ----------------- library/std/src/io/mod.rs | 15 +- .../mut-borrow-needed-by-trait.stderr | 4 +- .../ui/suggestions/suggest-change-mut.stderr | 2 +- 11 files changed, 306 insertions(+), 218 deletions(-) rename library/{std => alloc}/src/io/buffered/bufreader.rs (87%) rename library/{std => alloc}/src/io/buffered/bufreader/buffer.rs (93%) rename library/{std => alloc}/src/io/buffered/bufwriter.rs (95%) rename library/{std => alloc}/src/io/buffered/linewriter.rs (98%) rename library/{std => alloc}/src/io/buffered/linewritershim.rs (99%) create mode 100644 library/alloc/src/io/buffered/mod.rs diff --git a/library/std/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs similarity index 87% rename from library/std/src/io/buffered/bufreader.rs rename to library/alloc/src/io/buffered/bufreader.rs index 8d122c8e76e5f..9a912be8e18f0 100644 --- a/library/std/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -1,12 +1,14 @@ mod buffer; -use buffer::Buffer; +pub(super) use buffer::Buffer; use crate::fmt; use crate::io::{ self, BorrowedCursor, BufRead, DEFAULT_BUF_SIZE, IoSliceMut, Read, Seek, SeekFrom, SizeHint, SpecReadByte, uninlined_slow_read_byte, }; +use crate::string::String; +use crate::vec::Vec; /// The `BufReader` struct adds buffering to any reader. /// @@ -27,8 +29,9 @@ use crate::io::{ /// unwrapping the `BufReader` with [`BufReader::into_inner`] can also cause /// data loss. /// -/// [`TcpStream::read`]: crate::net::TcpStream::read -/// [`TcpStream`]: crate::net::TcpStream +// FIXME(#74481): Hard-links required to link from `alloc` to `std` +/// [`TcpStream::read`]: ../../std/net/struct.TcpStream.html#method.read +/// [`TcpStream`]: ../../std/net/struct.TcpStream.html /// /// # Examples /// @@ -70,16 +73,21 @@ impl BufReader { /// Ok(()) /// } /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn new(inner: R) -> BufReader { BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) } - pub(crate) fn try_new_buffer() -> io::Result { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn try_new_buffer() -> io::Result { Buffer::try_with_capacity(DEFAULT_BUF_SIZE) } - pub(crate) fn with_buffer(inner: R, buf: Buffer) -> Self { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn with_buffer(inner: R, buf: Buffer) -> Self { Self { inner, buf } } @@ -99,6 +107,7 @@ impl BufReader { /// Ok(()) /// } /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(capacity: usize, inner: R) -> BufReader { BufReader { inner, buf: Buffer::with_capacity(capacity) } @@ -280,14 +289,17 @@ impl BufReader { /// Invalidates all data in the internal buffer. #[inline] - pub(in crate::io) fn discard_buffer(&mut self) { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn discard_buffer(&mut self) { self.buf.discard_buffer() } } // This is only used by a test which asserts that the initialization-tracking is correct. -#[cfg(test)] impl BufReader { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] #[allow(missing_docs)] pub fn initialized(&self) -> bool { self.buf.initialized() @@ -413,7 +425,28 @@ impl Read for BufReader { fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { let inner_buf = self.buffer(); buf.try_reserve(inner_buf.len())?; - buf.extend_from_slice(inner_buf); + + cfg_select! { + no_global_oom_handling => { + // SAFETY: + // * inner_buf and buf are non-overlapping + // * buf[..len] is already initialized + // * buf[len..len + count] is initialized by copy_nonoverlapping + // * len + count is within the capacity of buf based on the reservation completed above + unsafe { + let count = inner_buf.len(); + let len = buf.len(); + let src = inner_buf.as_ptr(); + let dst = buf.as_mut_ptr().add(len); + core::ptr::copy_nonoverlapping(src, dst, count); + buf.set_len(len + count); + } + } + _ => { + buf.extend_from_slice(inner_buf); + } + } + let nread = inner_buf.len(); self.discard_buffer(); Ok(nread + self.inner.read_to_end(buf)?) @@ -445,7 +478,31 @@ impl Read for BufReader { let mut bytes = Vec::new(); self.read_to_end(&mut bytes)?; let string = crate::str::from_utf8(&bytes).map_err(|_| io::Error::INVALID_UTF8)?; - *buf += string; + + cfg_select! { + no_global_oom_handling => { + // SAFETY: + // * string and buf are non-overlapping + // * buf[..len] is already initialized + // * buf[len..len + count] is initialized by copy_nonoverlapping + // * len + count is within the capacity of buf based on the reservation completed above + // * buf is appended with valid UTF-8 data and is initially valid UTF-8, therefore + // it is valid UTF-8 at all times. + unsafe { + let buf = buf.as_mut_vec(); + let count = string.len(); + let len = buf.len(); + let src = string.as_ptr(); + let dst = buf.as_mut_ptr().add(len); + core::ptr::copy_nonoverlapping(src, dst, count); + buf.set_len(len + count); + } + } + _ => { + *buf += string; + } + } + Ok(string.len()) } } diff --git a/library/std/src/io/buffered/bufreader/buffer.rs b/library/alloc/src/io/buffered/bufreader/buffer.rs similarity index 93% rename from library/std/src/io/buffered/bufreader/buffer.rs rename to library/alloc/src/io/buffered/bufreader/buffer.rs index f2efcc5ab8601..a267208987238 100644 --- a/library/std/src/io/buffered/bufreader/buffer.rs +++ b/library/alloc/src/io/buffered/bufreader/buffer.rs @@ -9,10 +9,13 @@ //! that user code which wants to do reads from a `BufReader` via `buffer` + `consume` can do so //! without encountering any runtime bounds checks. -use crate::cmp; +use core::cmp; +use core::mem::MaybeUninit; + +use crate::boxed::Box; use crate::io::{self, BorrowedBuf, ErrorKind, Read}; -use crate::mem::MaybeUninit; +#[expect(missing_debug_implementations)] pub struct Buffer { // The buffer. buf: Box<[MaybeUninit]>, @@ -29,12 +32,15 @@ pub struct Buffer { } impl Buffer { + #[cfg(not(no_global_oom_handling))] #[inline] pub fn with_capacity(capacity: usize) -> Self { let buf = Box::new_uninit_slice(capacity); Self { buf, pos: 0, filled: 0, initialized: false } } + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] #[inline] pub fn try_with_capacity(capacity: usize) -> io::Result { match Box::try_new_uninit_slice(capacity) { @@ -68,7 +74,8 @@ impl Buffer { } // This is only used by a test which asserts that the initialization-tracking is correct. - #[cfg(test)] + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub fn initialized(&self) -> bool { self.initialized } diff --git a/library/std/src/io/buffered/bufwriter.rs b/library/alloc/src/io/buffered/bufwriter.rs similarity index 95% rename from library/std/src/io/buffered/bufwriter.rs rename to library/alloc/src/io/buffered/bufwriter.rs index 1a5cc911c0e43..59fa1f230fd3c 100644 --- a/library/std/src/io/buffered/bufwriter.rs +++ b/library/alloc/src/io/buffered/bufwriter.rs @@ -1,8 +1,10 @@ +use core::mem::{self, ManuallyDrop}; +use core::{error, fmt, ptr}; + use crate::io::{ self, DEFAULT_BUF_SIZE, ErrorKind, IntoInnerError, IoSlice, Seek, SeekFrom, Write, }; -use crate::mem::{self, ManuallyDrop}; -use crate::{error, fmt, ptr}; +use crate::vec::Vec; /// Wraps a writer and buffers its output. /// @@ -60,8 +62,9 @@ use crate::{error, fmt, ptr}; /// together by the buffer and will all be written out in one system call when /// the `stream` is flushed. /// -/// [`TcpStream::write`]: crate::net::TcpStream::write -/// [`TcpStream`]: crate::net::TcpStream +// FIXME(#74481): Hard-links required to link from `alloc` to `std` +/// [`TcpStream::write`]: ../../std/net/struct.TcpStream.html#method.write +/// [`TcpStream`]: ../../std/net/struct.TcpStream.html /// [`flush`]: BufWriter::flush #[stable(feature = "rust1", since = "1.0.0")] pub struct BufWriter { @@ -87,20 +90,26 @@ impl BufWriter { /// use std::io::BufWriter; /// use std::net::TcpStream; /// + /// # #[expect(unused_mut)] /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn new(inner: W) -> BufWriter { BufWriter::with_capacity(DEFAULT_BUF_SIZE, inner) } - pub(crate) fn try_new_buffer() -> io::Result> { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn try_new_buffer() -> io::Result> { Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| { io::const_error!(ErrorKind::OutOfMemory, "failed to allocate write buffer") }) } - pub(crate) fn with_buffer(inner: W, buf: Vec) -> Self { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn with_buffer(inner: W, buf: Vec) -> Self { Self { inner, buf, panicked: false } } @@ -115,8 +124,10 @@ impl BufWriter { /// use std::net::TcpStream; /// /// let stream = TcpStream::connect("127.0.0.1:34254").unwrap(); + /// # #[expect(unused_mut)] /// let mut buffer = BufWriter::with_capacity(100, stream); /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(capacity: usize, inner: W) -> BufWriter { BufWriter { inner, buf: Vec::with_capacity(capacity), panicked: false } @@ -136,6 +147,7 @@ impl BufWriter { /// use std::io::BufWriter; /// use std::net::TcpStream; /// + /// # #[expect(unused_mut)] /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); /// /// // unwrap the TcpStream and flush the buffer @@ -192,7 +204,9 @@ impl BufWriter { /// "successfully written" (by returning nonzero success values from /// `write`), any 0-length writes from `inner` must be reported as i/o /// errors from this method. - pub(in crate::io) fn flush_buf(&mut self) -> io::Result<()> { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn flush_buf(&mut self) -> io::Result<()> { // SAFETY: `::copy_from` assumes that // this will not de-initialize any elements of `self.buf`'s spare // capacity. @@ -287,6 +301,7 @@ impl BufWriter { /// use std::io::BufWriter; /// use std::net::TcpStream; /// + /// # #[expect(unused_mut)] /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); /// /// // we can use reference just like buffer @@ -343,7 +358,9 @@ impl BufWriter { /// That the buffer is a `Vec` is an implementation detail. /// Callers should not modify the capacity as there currently is no public API to do so /// and thus any capacity changes would be unexpected by the user. - pub(in crate::io) fn buffer_mut(&mut self) -> &mut Vec { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn buffer_mut(&mut self) -> &mut Vec { &mut self.buf } diff --git a/library/std/src/io/buffered/linewriter.rs b/library/alloc/src/io/buffered/linewriter.rs similarity index 98% rename from library/std/src/io/buffered/linewriter.rs rename to library/alloc/src/io/buffered/linewriter.rs index cc6921b86dd0b..bdb979e931f2e 100644 --- a/library/std/src/io/buffered/linewriter.rs +++ b/library/alloc/src/io/buffered/linewriter.rs @@ -84,6 +84,7 @@ impl LineWriter { /// Ok(()) /// } /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn new(inner: W) -> LineWriter { // Lines typically aren't that long, don't use a giant buffer @@ -105,6 +106,7 @@ impl LineWriter { /// Ok(()) /// } /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(capacity: usize, inner: W) -> LineWriter { LineWriter { inner: BufWriter::with_capacity(capacity, inner) } diff --git a/library/std/src/io/buffered/linewritershim.rs b/library/alloc/src/io/buffered/linewritershim.rs similarity index 99% rename from library/std/src/io/buffered/linewritershim.rs rename to library/alloc/src/io/buffered/linewritershim.rs index fc250e6ab7de9..35f448fc3aa8a 100644 --- a/library/std/src/io/buffered/linewritershim.rs +++ b/library/alloc/src/io/buffered/linewritershim.rs @@ -13,12 +13,12 @@ use crate::io::{self, BufWriter, IoSlice, Write}; /// `BufWriters` to be temporarily given line-buffering logic; this is what /// enables Stdout to be alternately in line-buffered or block-buffered mode. #[derive(Debug)] -pub struct LineWriterShim<'a, W: ?Sized + Write> { +pub(super) struct LineWriterShim<'a, W: ?Sized + Write> { buffer: &'a mut BufWriter, } impl<'a, W: ?Sized + Write> LineWriterShim<'a, W> { - pub fn new(buffer: &'a mut BufWriter) -> Self { + pub(super) fn new(buffer: &'a mut BufWriter) -> Self { Self { buffer } } diff --git a/library/alloc/src/io/buffered/mod.rs b/library/alloc/src/io/buffered/mod.rs new file mode 100644 index 0000000000000..1bddcfb801932 --- /dev/null +++ b/library/alloc/src/io/buffered/mod.rs @@ -0,0 +1,189 @@ +//! Buffering wrappers for I/O traits + +mod bufreader; +mod bufwriter; +mod linewriter; +mod linewritershim; + +use core::{error, fmt}; + +#[stable(feature = "bufwriter_into_parts", since = "1.56.0")] +pub use self::bufwriter::WriterPanicked; +use self::linewritershim::LineWriterShim; +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::{bufreader::BufReader, bufwriter::BufWriter, linewriter::LineWriter}; +use crate::io::Error; + +/// An error returned by [`BufWriter::into_inner`] which combines an error that +/// happened while writing out the buffer, and the buffered writer object +/// which may be used to recover from the condition. +/// +/// # Examples +/// +/// ```no_run +/// use std::io::BufWriter; +/// use std::net::TcpStream; +/// +/// # #[expect(unused_mut)] +/// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); +/// +/// // do stuff with the stream +/// +/// // we want to get our `TcpStream` back, so let's try: +/// +/// let stream = match stream.into_inner() { +/// Ok(s) => s, +/// Err(e) => { +/// // Here, e is an IntoInnerError +/// panic!("An error occurred"); +/// } +/// }; +/// ``` +#[derive(Debug)] +#[stable(feature = "rust1", since = "1.0.0")] +pub struct IntoInnerError(W, Error); + +impl IntoInnerError { + /// Constructs a new IntoInnerError + fn new(writer: W, error: Error) -> Self { + Self(writer, error) + } + + /// Helper to construct a new IntoInnerError; intended to help with + /// adapters that wrap other adapters + fn new_wrapped(self, f: impl FnOnce(W) -> W2) -> IntoInnerError { + let Self(writer, error) = self; + IntoInnerError::new(f(writer), error) + } + + /// Returns the error which caused the call to [`BufWriter::into_inner()`] + /// to fail. + /// + /// This error was returned when attempting to write the internal buffer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// # #[expect(unused_mut)] + /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// + /// // do stuff with the stream + /// + /// // we want to get our `TcpStream` back, so let's try: + /// + /// let stream = match stream.into_inner() { + /// Ok(s) => s, + /// Err(e) => { + /// // Here, e is an IntoInnerError, let's log the inner error. + /// // + /// // We'll just 'log' to stdout for this example. + /// println!("{}", e.error()); + /// + /// panic!("An unexpected error occurred."); + /// } + /// }; + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + pub fn error(&self) -> &Error { + &self.1 + } + + /// Returns the buffered writer instance which generated the error. + /// + /// The returned object can be used for error recovery, such as + /// re-inspecting the buffer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// # #[expect(unused_mut)] + /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// + /// // do stuff with the stream + /// + /// // we want to get our `TcpStream` back, so let's try: + /// + /// let stream = match stream.into_inner() { + /// Ok(s) => s, + /// Err(e) => { + /// // Here, e is an IntoInnerError, let's re-examine the buffer: + /// let buffer = e.into_inner(); + /// + /// // do stuff to try to recover + /// + /// // afterwards, let's just return the stream + /// buffer.into_inner().unwrap() + /// } + /// }; + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + pub fn into_inner(self) -> W { + self.0 + } + + /// Consumes the [`IntoInnerError`] and returns the error which caused the call to + /// [`BufWriter::into_inner()`] to fail. Unlike `error`, this can be used to + /// obtain ownership of the underlying error. + /// + /// # Example + /// ``` + /// use std::io::{BufWriter, ErrorKind, Write}; + /// + /// let mut not_enough_space = [0u8; 10]; + /// let mut stream = BufWriter::new(not_enough_space.as_mut()); + /// write!(stream, "this cannot be actually written").unwrap(); + /// let into_inner_err = stream.into_inner().expect_err("now we discover it's too small"); + /// let err = into_inner_err.into_error(); + /// assert_eq!(err.kind(), ErrorKind::WriteZero); + /// ``` + #[stable(feature = "io_into_inner_error_parts", since = "1.55.0")] + pub fn into_error(self) -> Error { + self.1 + } + + /// Consumes the [`IntoInnerError`] and returns the error which caused the call to + /// [`BufWriter::into_inner()`] to fail, and the underlying writer. + /// + /// This can be used to simply obtain ownership of the underlying error; it can also be used for + /// advanced error recovery. + /// + /// # Example + /// ``` + /// use std::io::{BufWriter, ErrorKind, Write}; + /// + /// let mut not_enough_space = [0u8; 10]; + /// let mut stream = BufWriter::new(not_enough_space.as_mut()); + /// write!(stream, "this cannot be actually written").unwrap(); + /// let into_inner_err = stream.into_inner().expect_err("now we discover it's too small"); + /// let (err, recovered_writer) = into_inner_err.into_parts(); + /// assert_eq!(err.kind(), ErrorKind::WriteZero); + /// assert_eq!(recovered_writer.buffer(), b"t be actually written"); + /// ``` + #[stable(feature = "io_into_inner_error_parts", since = "1.55.0")] + pub fn into_parts(self) -> (Error, W) { + (self.1, self.0) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl From> for Error { + fn from(iie: IntoInnerError) -> Error { + iie.1 + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl error::Error for IntoInnerError {} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Display for IntoInnerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.error().fmt(f) + } +} diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index ff39cb145a929..dd2fffe8429f9 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -1,6 +1,7 @@ //! Traits, helpers, and type definitions for core I/O functionality. mod buf_read; +mod buffered; mod cursor; mod error; mod impls; @@ -31,6 +32,7 @@ pub use core::io::{ #[unstable(feature = "alloc_io", issue = "154046")] pub use self::{ buf_read::BufRead, + buffered::{BufReader, BufWriter, IntoInnerError, LineWriter, WriterPanicked}, read::{Read, read_to_string}, util::{Bytes, Lines, Split}, }; diff --git a/library/std/src/io/buffered/mod.rs b/library/std/src/io/buffered/mod.rs index e36f2d92108d0..1d09ff7d8dc1c 100644 --- a/library/std/src/io/buffered/mod.rs +++ b/library/std/src/io/buffered/mod.rs @@ -1,189 +1,4 @@ //! Buffering wrappers for I/O traits -mod bufreader; -mod bufwriter; -mod linewriter; -mod linewritershim; - #[cfg(test)] mod tests; - -#[stable(feature = "bufwriter_into_parts", since = "1.56.0")] -pub use bufwriter::WriterPanicked; -use linewritershim::LineWriterShim; - -#[stable(feature = "rust1", since = "1.0.0")] -pub use self::{bufreader::BufReader, bufwriter::BufWriter, linewriter::LineWriter}; -use crate::io::Error; -use crate::{error, fmt}; - -/// An error returned by [`BufWriter::into_inner`] which combines an error that -/// happened while writing out the buffer, and the buffered writer object -/// which may be used to recover from the condition. -/// -/// # Examples -/// -/// ```no_run -/// use std::io::BufWriter; -/// use std::net::TcpStream; -/// -/// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); -/// -/// // do stuff with the stream -/// -/// // we want to get our `TcpStream` back, so let's try: -/// -/// let stream = match stream.into_inner() { -/// Ok(s) => s, -/// Err(e) => { -/// // Here, e is an IntoInnerError -/// panic!("An error occurred"); -/// } -/// }; -/// ``` -#[derive(Debug)] -#[stable(feature = "rust1", since = "1.0.0")] -pub struct IntoInnerError(W, Error); - -impl IntoInnerError { - /// Constructs a new IntoInnerError - fn new(writer: W, error: Error) -> Self { - Self(writer, error) - } - - /// Helper to construct a new IntoInnerError; intended to help with - /// adapters that wrap other adapters - fn new_wrapped(self, f: impl FnOnce(W) -> W2) -> IntoInnerError { - let Self(writer, error) = self; - IntoInnerError::new(f(writer), error) - } - - /// Returns the error which caused the call to [`BufWriter::into_inner()`] - /// to fail. - /// - /// This error was returned when attempting to write the internal buffer. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::BufWriter; - /// use std::net::TcpStream; - /// - /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); - /// - /// // do stuff with the stream - /// - /// // we want to get our `TcpStream` back, so let's try: - /// - /// let stream = match stream.into_inner() { - /// Ok(s) => s, - /// Err(e) => { - /// // Here, e is an IntoInnerError, let's log the inner error. - /// // - /// // We'll just 'log' to stdout for this example. - /// println!("{}", e.error()); - /// - /// panic!("An unexpected error occurred."); - /// } - /// }; - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - pub fn error(&self) -> &Error { - &self.1 - } - - /// Returns the buffered writer instance which generated the error. - /// - /// The returned object can be used for error recovery, such as - /// re-inspecting the buffer. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::BufWriter; - /// use std::net::TcpStream; - /// - /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); - /// - /// // do stuff with the stream - /// - /// // we want to get our `TcpStream` back, so let's try: - /// - /// let stream = match stream.into_inner() { - /// Ok(s) => s, - /// Err(e) => { - /// // Here, e is an IntoInnerError, let's re-examine the buffer: - /// let buffer = e.into_inner(); - /// - /// // do stuff to try to recover - /// - /// // afterwards, let's just return the stream - /// buffer.into_inner().unwrap() - /// } - /// }; - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - pub fn into_inner(self) -> W { - self.0 - } - - /// Consumes the [`IntoInnerError`] and returns the error which caused the call to - /// [`BufWriter::into_inner()`] to fail. Unlike `error`, this can be used to - /// obtain ownership of the underlying error. - /// - /// # Example - /// ``` - /// use std::io::{BufWriter, ErrorKind, Write}; - /// - /// let mut not_enough_space = [0u8; 10]; - /// let mut stream = BufWriter::new(not_enough_space.as_mut()); - /// write!(stream, "this cannot be actually written").unwrap(); - /// let into_inner_err = stream.into_inner().expect_err("now we discover it's too small"); - /// let err = into_inner_err.into_error(); - /// assert_eq!(err.kind(), ErrorKind::WriteZero); - /// ``` - #[stable(feature = "io_into_inner_error_parts", since = "1.55.0")] - pub fn into_error(self) -> Error { - self.1 - } - - /// Consumes the [`IntoInnerError`] and returns the error which caused the call to - /// [`BufWriter::into_inner()`] to fail, and the underlying writer. - /// - /// This can be used to simply obtain ownership of the underlying error; it can also be used for - /// advanced error recovery. - /// - /// # Example - /// ``` - /// use std::io::{BufWriter, ErrorKind, Write}; - /// - /// let mut not_enough_space = [0u8; 10]; - /// let mut stream = BufWriter::new(not_enough_space.as_mut()); - /// write!(stream, "this cannot be actually written").unwrap(); - /// let into_inner_err = stream.into_inner().expect_err("now we discover it's too small"); - /// let (err, recovered_writer) = into_inner_err.into_parts(); - /// assert_eq!(err.kind(), ErrorKind::WriteZero); - /// assert_eq!(recovered_writer.buffer(), b"t be actually written"); - /// ``` - #[stable(feature = "io_into_inner_error_parts", since = "1.55.0")] - pub fn into_parts(self) -> (Error, W) { - (self.1, self.0) - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl From> for Error { - fn from(iie: IntoInnerError) -> Error { - iie.1 - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl error::Error for IntoInnerError {} - -#[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Display for IntoInnerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.error().fmt(f) - } -} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index dbc92d7b45d7c..572f0fb3e4611 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -297,11 +297,14 @@ #[cfg(test)] mod tests; +use alloc_crate::io::OsFunctions; #[unstable(feature = "raw_os_error_ty", issue = "107792")] pub use alloc_crate::io::RawOsError; #[doc(hidden)] #[unstable(feature = "io_const_error_internals", issue = "none")] pub use alloc_crate::io::SimpleMessage; +#[stable(feature = "bufwriter_into_parts", since = "1.56.0")] +pub use alloc_crate::io::WriterPanicked; #[unstable(feature = "io_const_error", issue = "133448")] pub use alloc_crate::io::const_error; #[stable(feature = "io_read_to_string", since = "1.65.0")] @@ -310,23 +313,20 @@ pub use alloc_crate::io::read_to_string; pub use alloc_crate::io::{BorrowedBuf, BorrowedCursor}; #[stable(feature = "rust1", since = "1.0.0")] pub use alloc_crate::io::{ - BufRead, Bytes, Chain, Cursor, Empty, Error, ErrorKind, Lines, Read, Repeat, Result, Seek, - SeekFrom, Sink, Split, Take, Write, empty, repeat, sink, + BufRead, BufReader, BufWriter, Bytes, Chain, Cursor, Empty, Error, ErrorKind, IntoInnerError, + LineWriter, Lines, Read, Repeat, Result, Seek, SeekFrom, Sink, Split, Take, Write, empty, + repeat, sink, }; #[allow(unused_imports, reason = "only used by certain platforms")] pub(crate) use alloc_crate::io::{ DEFAULT_BUF_SIZE, default_read_buf, default_read_vectored, default_write_vectored, }; pub(crate) use alloc_crate::io::{ - IoHandle, SpecReadByte, append_to_string, default_read_buf_exact, default_read_exact, - default_read_to_end, default_read_to_string, stream_len_default, uninlined_slow_read_byte, + IoHandle, SpecReadByte, default_read_to_end, default_read_to_string, stream_len_default, }; #[stable(feature = "iovec", since = "1.36.0")] pub use alloc_crate::io::{IoSlice, IoSliceMut}; -use alloc_crate::io::{OsFunctions, SizeHint}; -#[stable(feature = "bufwriter_into_parts", since = "1.56.0")] -pub use self::buffered::WriterPanicked; #[stable(feature = "anonymous_pipe", since = "1.87.0")] pub use self::pipe::{PipeReader, PipeWriter, pipe}; #[stable(feature = "is_terminal", since = "1.70.0")] @@ -340,7 +340,6 @@ pub use self::stdio::{_eprint, _print}; pub use self::stdio::{set_output_capture, try_set_output_capture}; #[stable(feature = "rust1", since = "1.0.0")] pub use self::{ - buffered::{BufReader, BufWriter, IntoInnerError, LineWriter}, copy::copy, stdio::{Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout}, }; diff --git a/tests/ui/suggestions/mut-borrow-needed-by-trait.stderr b/tests/ui/suggestions/mut-borrow-needed-by-trait.stderr index eadf512a63b49..5ba7fcb2479bb 100644 --- a/tests/ui/suggestions/mut-borrow-needed-by-trait.stderr +++ b/tests/ui/suggestions/mut-borrow-needed-by-trait.stderr @@ -8,7 +8,7 @@ LL | let fp = BufWriter::new(fp); | = note: `std::io::Write` is implemented for `&mut dyn std::io::Write`, but not for `&dyn std::io::Write` note: required by a bound in `BufWriter::::new` - --> $SRC_DIR/std/src/io/buffered/bufwriter.rs:LL:COL + --> $SRC_DIR/alloc/src/io/buffered/bufwriter.rs:LL:COL error[E0277]: the trait bound `&dyn std::io::Write: std::io::Write` is not satisfied --> $DIR/mut-borrow-needed-by-trait.rs:17:14 @@ -18,7 +18,7 @@ LL | let fp = BufWriter::new(fp); | = note: `std::io::Write` is implemented for `&mut dyn std::io::Write`, but not for `&dyn std::io::Write` note: required by a bound in `BufWriter` - --> $SRC_DIR/std/src/io/buffered/bufwriter.rs:LL:COL + --> $SRC_DIR/alloc/src/io/buffered/bufwriter.rs:LL:COL error[E0599]: the method `write_fmt` exists for struct `BufWriter<&dyn std::io::Write>`, but its trait bounds were not satisfied --> $DIR/mut-borrow-needed-by-trait.rs:21:14 diff --git a/tests/ui/suggestions/suggest-change-mut.stderr b/tests/ui/suggestions/suggest-change-mut.stderr index c47ae433ab896..fc664c92ceee2 100644 --- a/tests/ui/suggestions/suggest-change-mut.stderr +++ b/tests/ui/suggestions/suggest-change-mut.stderr @@ -7,7 +7,7 @@ LL | let mut stream_reader = BufReader::new(&stream); | required by a bound introduced by this call | note: required by a bound in `BufReader::::new` - --> $SRC_DIR/std/src/io/buffered/bufreader.rs:LL:COL + --> $SRC_DIR/alloc/src/io/buffered/bufreader.rs:LL:COL help: consider removing the leading `&`-reference | LL - let mut stream_reader = BufReader::new(&stream); From f415df6e727215b61153774c5866bfc0a1772d6d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 26 May 2026 09:16:54 +1000 Subject: [PATCH 02/11] Reduce API surface of `Buffer` None of the currently public methods are accessible outside `std`, and are unused within. Therefore, they can be restricted to internal use. --- .../alloc/src/io/buffered/bufreader/buffer.rs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/library/alloc/src/io/buffered/bufreader/buffer.rs b/library/alloc/src/io/buffered/bufreader/buffer.rs index a267208987238..3c88e705c7011 100644 --- a/library/alloc/src/io/buffered/bufreader/buffer.rs +++ b/library/alloc/src/io/buffered/bufreader/buffer.rs @@ -15,7 +15,9 @@ use core::mem::MaybeUninit; use crate::boxed::Box; use crate::io::{self, BorrowedBuf, ErrorKind, Read}; -#[expect(missing_debug_implementations)] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +#[derive(Debug)] pub struct Buffer { // The buffer. buf: Box<[MaybeUninit]>, @@ -34,15 +36,13 @@ pub struct Buffer { impl Buffer { #[cfg(not(no_global_oom_handling))] #[inline] - pub fn with_capacity(capacity: usize) -> Self { + pub(super) fn with_capacity(capacity: usize) -> Self { let buf = Box::new_uninit_slice(capacity); Self { buf, pos: 0, filled: 0, initialized: false } } - #[doc(hidden)] - #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] #[inline] - pub fn try_with_capacity(capacity: usize) -> io::Result { + pub(super) fn try_with_capacity(capacity: usize) -> io::Result { match Box::try_new_uninit_slice(capacity) { Ok(buf) => Ok(Self { buf, pos: 0, filled: 0, initialized: false }), Err(_) => { @@ -52,49 +52,47 @@ impl Buffer { } #[inline] - pub fn buffer(&self) -> &[u8] { + pub(super) fn buffer(&self) -> &[u8] { // SAFETY: self.pos and self.filled are valid, and self.filled >= self.pos, and // that region is initialized because those are all invariants of this type. unsafe { self.buf.get_unchecked(self.pos..self.filled).assume_init_ref() } } #[inline] - pub fn capacity(&self) -> usize { + pub(super) fn capacity(&self) -> usize { self.buf.len() } #[inline] - pub fn filled(&self) -> usize { + pub(super) fn filled(&self) -> usize { self.filled } #[inline] - pub fn pos(&self) -> usize { + pub(super) fn pos(&self) -> usize { self.pos } // This is only used by a test which asserts that the initialization-tracking is correct. - #[doc(hidden)] - #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn initialized(&self) -> bool { + pub(super) fn initialized(&self) -> bool { self.initialized } #[inline] - pub fn discard_buffer(&mut self) { + pub(super) fn discard_buffer(&mut self) { self.pos = 0; self.filled = 0; } #[inline] - pub fn consume(&mut self, amt: usize) { + pub(super) fn consume(&mut self, amt: usize) { self.pos = cmp::min(self.pos + amt, self.filled); } /// If there are `amt` bytes available in the buffer, pass a slice containing those bytes to /// `visitor` and return true. If there are not enough bytes available, return false. #[inline] - pub fn consume_with(&mut self, amt: usize, mut visitor: V) -> bool + pub(super) fn consume_with(&mut self, amt: usize, mut visitor: V) -> bool where V: FnMut(&[u8]), { @@ -109,12 +107,12 @@ impl Buffer { } #[inline] - pub fn unconsume(&mut self, amt: usize) { + pub(super) fn unconsume(&mut self, amt: usize) { self.pos = self.pos.saturating_sub(amt); } /// Read more bytes into the buffer without discarding any of its contents - pub fn read_more(&mut self, mut reader: impl Read) -> io::Result { + pub(super) fn read_more(&mut self, mut reader: impl Read) -> io::Result { let mut buf = BorrowedBuf::from(&mut self.buf[self.filled..]); if self.initialized { @@ -131,14 +129,14 @@ impl Buffer { } /// Remove bytes that have already been read from the buffer. - pub fn backshift(&mut self) { + pub(super) fn backshift(&mut self) { self.buf.copy_within(self.pos..self.filled, 0); self.filled -= self.pos; self.pos = 0; } #[inline] - pub fn fill_buf(&mut self, mut reader: impl Read) -> io::Result<&[u8]> { + pub(super) fn fill_buf(&mut self, mut reader: impl Read) -> io::Result<&[u8]> { // If we've reached the end of our internal buffer then we need to fetch // some more data from the reader. // Branch using `>=` instead of the more correct `==` From 4af788e0ba851878b759ce55d83481c2f758854b Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 19 Jul 2026 20:40:16 +1000 Subject: [PATCH 03/11] Reduce API surface of `alloc::io::util` --- library/alloc/src/io/mod.rs | 3 ++- library/alloc/src/io/util.rs | 16 ++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index dd2fffe8429f9..ad702c90afa69 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -29,6 +29,7 @@ pub use core::io::{ stream_len_default, take, }; +use self::util::{bytes, lines, split, uninlined_slow_read_byte}; #[unstable(feature = "alloc_io", issue = "154046")] pub use self::{ buf_read::BufRead, @@ -43,5 +44,5 @@ pub use self::{ DEFAULT_BUF_SIZE, append_to_string, default_read_buf, default_read_buf_exact, default_read_exact, default_read_to_end, default_read_to_string, default_read_vectored, }, - util::{SpecReadByte, bytes, lines, split, uninlined_slow_read_byte}, + util::SpecReadByte, }; diff --git a/library/alloc/src/io/util.rs b/library/alloc/src/io/util.rs index 050e018e8568f..567a041be5b78 100644 --- a/library/alloc/src/io/util.rs +++ b/library/alloc/src/io/util.rs @@ -388,15 +388,11 @@ fn inlined_slow_read_byte(reader: &mut R) -> Option> { // Used by `BufReader::spec_read_byte`, for which the `inline(never)` is // important. #[inline(never)] -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn uninlined_slow_read_byte(reader: &mut R) -> Option> { +pub(in crate::io) fn uninlined_slow_read_byte(reader: &mut R) -> Option> { inlined_slow_read_byte(reader) } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub const fn bytes(inner: R) -> Bytes { +pub(in crate::io) const fn bytes(inner: R) -> Bytes { Bytes { inner } } @@ -434,9 +430,7 @@ impl Iterator for Split { } } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub const fn split(buf: B, delim: u8) -> Split { +pub(in crate::io) const fn split(buf: B, delim: u8) -> Split { Split { buf, delim } } @@ -475,8 +469,6 @@ impl Iterator for Lines { } } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub const fn lines(buf: B) -> Lines { +pub(in crate::io) const fn lines(buf: B) -> Lines { Lines { buf } } From 4f8a2a924511ad7180010c8eee5bcfc7861fcdc8 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 19 Jul 2026 20:41:02 +1000 Subject: [PATCH 04/11] Reduce API surface of `alloc::io::read` --- library/alloc/src/io/mod.rs | 5 +++-- library/alloc/src/io/read.rs | 15 ++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index ad702c90afa69..75bbd2f0adc1b 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -29,6 +29,7 @@ pub use core::io::{ stream_len_default, take, }; +use self::read::{append_to_string, default_read_buf_exact, default_read_exact}; use self::util::{bytes, lines, split, uninlined_slow_read_byte}; #[unstable(feature = "alloc_io", issue = "154046")] pub use self::{ @@ -41,8 +42,8 @@ pub use self::{ #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::{ read::{ - DEFAULT_BUF_SIZE, append_to_string, default_read_buf, default_read_buf_exact, - default_read_exact, default_read_to_end, default_read_to_string, default_read_vectored, + DEFAULT_BUF_SIZE, default_read_buf, default_read_to_end, default_read_to_string, + default_read_vectored, }, util::SpecReadByte, }; diff --git a/library/alloc/src/io/read.rs b/library/alloc/src/io/read.rs index c1356811cc58a..1b47c20b01962 100644 --- a/library/alloc/src/io/read.rs +++ b/library/alloc/src/io/read.rs @@ -795,9 +795,7 @@ pub const DEFAULT_BUF_SIZE: usize = cfg_select! { /// 2. We're passing a raw buffer to the function `f`, and it is expected that /// the function only *appends* bytes to the buffer. We'll get undefined /// behavior if existing bytes are overwritten to have non-UTF-8 data. -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub unsafe fn append_to_string(buf: &mut String, f: F) -> Result +pub(in crate::io) unsafe fn append_to_string(buf: &mut String, f: F) -> Result where F: FnOnce(&mut Vec) -> Result, { @@ -988,9 +986,10 @@ where read(buf) } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<()> { +pub(in crate::io) fn default_read_exact( + this: &mut R, + mut buf: &mut [u8], +) -> Result<()> { while !buf.is_empty() { match this.read(buf) { Ok(0) => break, @@ -1015,9 +1014,7 @@ where Ok(()) } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn default_read_buf_exact( +pub(in crate::io) fn default_read_buf_exact( this: &mut R, mut cursor: BorrowedCursor<'_, u8>, ) -> Result<()> { From 44d9de29f242057908355ffaa3ca56095bd7fea5 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 19 Jul 2026 20:42:02 +1000 Subject: [PATCH 05/11] Reduce API surface of `alloc::io` reexports from `core::io` --- library/alloc/src/io/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 75bbd2f0adc1b..ef09d13cc6247 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -23,10 +23,10 @@ pub use core::io::{ }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub use core::io::{ - IoHandle, OsFunctions, SizeHint, WriteThroughCursor, chain, default_write_vectored, - slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, - stream_len_default, take, +pub use core::io::{IoHandle, OsFunctions, default_write_vectored, stream_len_default}; +use core::io::{ + SizeHint, WriteThroughCursor, chain, slice_write, slice_write_all, slice_write_all_vectored, + slice_write_vectored, take, }; use self::read::{append_to_string, default_read_buf_exact, default_read_exact}; From f012e40a8372d6d25b2139f707d28dea70d9d6e4 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 20 Jul 2026 10:29:29 +1000 Subject: [PATCH 06/11] Use `String::try_push_str` instead of `unsafe` code Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/buffered/bufreader.rs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/library/alloc/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs index 9a912be8e18f0..c6039e19b1796 100644 --- a/library/alloc/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -481,25 +481,10 @@ impl Read for BufReader { cfg_select! { no_global_oom_handling => { - // SAFETY: - // * string and buf are non-overlapping - // * buf[..len] is already initialized - // * buf[len..len + count] is initialized by copy_nonoverlapping - // * len + count is within the capacity of buf based on the reservation completed above - // * buf is appended with valid UTF-8 data and is initially valid UTF-8, therefore - // it is valid UTF-8 at all times. - unsafe { - let buf = buf.as_mut_vec(); - let count = string.len(); - let len = buf.len(); - let src = string.as_ptr(); - let dst = buf.as_mut_ptr().add(len); - core::ptr::copy_nonoverlapping(src, dst, count); - buf.set_len(len + count); - } + buf.try_push_str(string)?; } _ => { - *buf += string; + buf.push_str(string); } } From 295f7f464a42c4b3f23a5fed0afea1899b6ceb4a Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 20 Jul 2026 10:29:54 +1000 Subject: [PATCH 07/11] Use `Vec::::try_extend_from_slice_of_bytes` instead of `unsafe` code Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/buffered/bufreader.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/library/alloc/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs index c6039e19b1796..ad1a03412f72f 100644 --- a/library/alloc/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -428,19 +428,7 @@ impl Read for BufReader { cfg_select! { no_global_oom_handling => { - // SAFETY: - // * inner_buf and buf are non-overlapping - // * buf[..len] is already initialized - // * buf[len..len + count] is initialized by copy_nonoverlapping - // * len + count is within the capacity of buf based on the reservation completed above - unsafe { - let count = inner_buf.len(); - let len = buf.len(); - let src = inner_buf.as_ptr(); - let dst = buf.as_mut_ptr().add(len); - core::ptr::copy_nonoverlapping(src, dst, count); - buf.set_len(len + count); - } + buf.try_extend_from_slice_of_bytes(inner_buf)?; } _ => { buf.extend_from_slice(inner_buf); From 3739d0425b35c374e9476699224e600830fed5b5 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 20 Jul 2026 11:09:38 +1000 Subject: [PATCH 08/11] Make `Buffer` private --- library/alloc/src/io/buffered/bufreader.rs | 16 +++++++--------- .../alloc/src/io/buffered/bufreader/buffer.rs | 5 +---- library/alloc/src/io/buffered/bufwriter.rs | 16 +++++++--------- library/std/src/fs.rs | 8 ++------ 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/library/alloc/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs index ad1a03412f72f..12216c70059d6 100644 --- a/library/alloc/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -1,6 +1,6 @@ mod buffer; -pub(super) use buffer::Buffer; +use buffer::Buffer; use crate::fmt; use crate::io::{ @@ -79,16 +79,14 @@ impl BufReader { BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) } + /// Attempts to allocate an internal buffer, _then_ calls the provided function + /// to retrieve the inner reader `R`. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn try_new_buffer() -> io::Result { - Buffer::try_with_capacity(DEFAULT_BUF_SIZE) - } - - #[doc(hidden)] - #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn with_buffer(inner: R, buf: Buffer) -> Self { - Self { inner, buf } + pub fn try_new_with(f: impl FnOnce() -> io::Result) -> io::Result { + let buf = Buffer::try_with_capacity(DEFAULT_BUF_SIZE)?; + let inner = f()?; + Ok(Self { inner, buf }) } /// Creates a new `BufReader` with the specified buffer capacity. diff --git a/library/alloc/src/io/buffered/bufreader/buffer.rs b/library/alloc/src/io/buffered/bufreader/buffer.rs index 3c88e705c7011..316889d93f576 100644 --- a/library/alloc/src/io/buffered/bufreader/buffer.rs +++ b/library/alloc/src/io/buffered/bufreader/buffer.rs @@ -15,10 +15,7 @@ use core::mem::MaybeUninit; use crate::boxed::Box; use crate::io::{self, BorrowedBuf, ErrorKind, Read}; -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -#[derive(Debug)] -pub struct Buffer { +pub(super) struct Buffer { // The buffer. buf: Box<[MaybeUninit]>, // The current seek offset into `buf`, must always be <= `filled`. diff --git a/library/alloc/src/io/buffered/bufwriter.rs b/library/alloc/src/io/buffered/bufwriter.rs index 59fa1f230fd3c..d36c0ccca8e89 100644 --- a/library/alloc/src/io/buffered/bufwriter.rs +++ b/library/alloc/src/io/buffered/bufwriter.rs @@ -99,18 +99,16 @@ impl BufWriter { BufWriter::with_capacity(DEFAULT_BUF_SIZE, inner) } + /// Attempts to allocate an internal buffer, _then_ calls the provided function + /// to retrieve the inner writer `W`. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn try_new_buffer() -> io::Result> { - Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| { + pub fn try_new_with(f: impl FnOnce() -> io::Result) -> io::Result { + let buf = Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| { io::const_error!(ErrorKind::OutOfMemory, "failed to allocate write buffer") - }) - } - - #[doc(hidden)] - #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn with_buffer(inner: W, buf: Vec) -> Self { - Self { inner, buf, panicked: false } + })?; + let inner = f()?; + Ok(Self { inner, buf, panicked: false }) } /// Creates a new `BufWriter` with at least the specified buffer capacity. diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 3574855e04dc9..388bf4a55e11f 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -604,9 +604,7 @@ impl File { #[unstable(feature = "file_buffered", issue = "130804")] pub fn open_buffered>(path: P) -> io::Result> { // Allocate the buffer *first* so we don't affect the filesystem otherwise. - let buffer = io::BufReader::::try_new_buffer()?; - let file = File::open(path)?; - Ok(io::BufReader::with_buffer(file, buffer)) + io::BufReader::try_new_with(|| File::open(path)) } /// Opens a file in write-only mode. @@ -672,9 +670,7 @@ impl File { #[unstable(feature = "file_buffered", issue = "130804")] pub fn create_buffered>(path: P) -> io::Result> { // Allocate the buffer *first* so we don't affect the filesystem otherwise. - let buffer = io::BufWriter::::try_new_buffer()?; - let file = File::create(path)?; - Ok(io::BufWriter::with_buffer(file, buffer)) + io::BufWriter::try_new_with(|| File::create(path)) } /// Creates a new file in read-write mode; error if the file exists. From 69b380676dc71c90a5f6122d1ab41ee101b21247 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 20 Jul 2026 11:28:13 +1000 Subject: [PATCH 09/11] Standardize on `pub(super)` instead of `pub(in crate::io)` Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/read.rs | 9 +++------ library/alloc/src/io/util.rs | 8 ++++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/library/alloc/src/io/read.rs b/library/alloc/src/io/read.rs index 1b47c20b01962..c0123c860b453 100644 --- a/library/alloc/src/io/read.rs +++ b/library/alloc/src/io/read.rs @@ -795,7 +795,7 @@ pub const DEFAULT_BUF_SIZE: usize = cfg_select! { /// 2. We're passing a raw buffer to the function `f`, and it is expected that /// the function only *appends* bytes to the buffer. We'll get undefined /// behavior if existing bytes are overwritten to have non-UTF-8 data. -pub(in crate::io) unsafe fn append_to_string(buf: &mut String, f: F) -> Result +pub(super) unsafe fn append_to_string(buf: &mut String, f: F) -> Result where F: FnOnce(&mut Vec) -> Result, { @@ -986,10 +986,7 @@ where read(buf) } -pub(in crate::io) fn default_read_exact( - this: &mut R, - mut buf: &mut [u8], -) -> Result<()> { +pub(super) fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<()> { while !buf.is_empty() { match this.read(buf) { Ok(0) => break, @@ -1014,7 +1011,7 @@ where Ok(()) } -pub(in crate::io) fn default_read_buf_exact( +pub(super) fn default_read_buf_exact( this: &mut R, mut cursor: BorrowedCursor<'_, u8>, ) -> Result<()> { diff --git a/library/alloc/src/io/util.rs b/library/alloc/src/io/util.rs index 567a041be5b78..9bf5a56dd7157 100644 --- a/library/alloc/src/io/util.rs +++ b/library/alloc/src/io/util.rs @@ -388,11 +388,11 @@ fn inlined_slow_read_byte(reader: &mut R) -> Option> { // Used by `BufReader::spec_read_byte`, for which the `inline(never)` is // important. #[inline(never)] -pub(in crate::io) fn uninlined_slow_read_byte(reader: &mut R) -> Option> { +pub(super) fn uninlined_slow_read_byte(reader: &mut R) -> Option> { inlined_slow_read_byte(reader) } -pub(in crate::io) const fn bytes(inner: R) -> Bytes { +pub(super) const fn bytes(inner: R) -> Bytes { Bytes { inner } } @@ -430,7 +430,7 @@ impl Iterator for Split { } } -pub(in crate::io) const fn split(buf: B, delim: u8) -> Split { +pub(super) const fn split(buf: B, delim: u8) -> Split { Split { buf, delim } } @@ -469,6 +469,6 @@ impl Iterator for Lines { } } -pub(in crate::io) const fn lines(buf: B) -> Lines { +pub(super) const fn lines(buf: B) -> Lines { Lines { buf } } From 9d38fe5be7ad7e9b34972387be109b814b9c4007 Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Mon, 20 Jul 2026 22:16:52 +1000 Subject: [PATCH 10/11] Add `inline` to new `BufReader` method --- library/alloc/src/io/buffered/bufreader.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/alloc/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs index 12216c70059d6..e8b3302e29b98 100644 --- a/library/alloc/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -83,6 +83,7 @@ impl BufReader { /// to retrieve the inner reader `R`. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + #[inline] pub fn try_new_with(f: impl FnOnce() -> io::Result) -> io::Result { let buf = Buffer::try_with_capacity(DEFAULT_BUF_SIZE)?; let inner = f()?; From 6ef1cf3cf0303352fb8a4528020dc28095003055 Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Mon, 20 Jul 2026 22:17:16 +1000 Subject: [PATCH 11/11] Add `inline` to new `BufWriter` method --- library/alloc/src/io/buffered/bufwriter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/alloc/src/io/buffered/bufwriter.rs b/library/alloc/src/io/buffered/bufwriter.rs index d36c0ccca8e89..4847c1605ac79 100644 --- a/library/alloc/src/io/buffered/bufwriter.rs +++ b/library/alloc/src/io/buffered/bufwriter.rs @@ -103,6 +103,7 @@ impl BufWriter { /// to retrieve the inner writer `W`. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + #[inline] pub fn try_new_with(f: impl FnOnce() -> io::Result) -> io::Result { let buf = Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| { io::const_error!(ErrorKind::OutOfMemory, "failed to allocate write buffer")