From 6735c2b25e15f818d063f7043712d088731a650e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Wed, 24 Dec 2025 16:41:01 +0800 Subject: [PATCH 1/6] refactor: reimplement BufReader --- Cargo.toml | 1 + src/buffered/bufreader.rs | 156 -------------- src/buffered/bufreader/buffer.rs | 177 ++++++++++++++++ src/buffered/bufreader/mod.rs | 350 +++++++++++++++++++++++++++++++ 4 files changed, 528 insertions(+), 156 deletions(-) delete mode 100644 src/buffered/bufreader.rs create mode 100644 src/buffered/bufreader/buffer.rs create mode 100644 src/buffered/bufreader/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 12e4f99..cc25aff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ continue-on-interrupt = [] [dependencies] axerrno = "0.2" +heapless = "0.9" memchr = { version = "2", default-features = false } [build-dependencies] diff --git a/src/buffered/bufreader.rs b/src/buffered/bufreader.rs deleted file mode 100644 index e65ef77..0000000 --- a/src/buffered/bufreader.rs +++ /dev/null @@ -1,156 +0,0 @@ -#[cfg(feature = "alloc")] -use alloc::{string::String, vec::Vec}; - -use crate::{BufRead, DEFAULT_BUF_SIZE, Read, Result}; - -/// The `BufReader` struct adds buffering to any reader. -pub struct BufReader { - inner: R, - pos: usize, - filled: usize, - buf: [u8; DEFAULT_BUF_SIZE], -} - -impl BufReader { - /// Creates a new `BufReader` with a default buffer capacity (1 KB). - pub const fn new(inner: R) -> BufReader { - Self { - inner, - pos: 0, - filled: 0, - buf: [0; DEFAULT_BUF_SIZE], - } - } -} - -impl BufReader { - /// Gets a reference to the underlying reader. - pub const fn get_ref(&self) -> &R { - &self.inner - } - - /// Gets a mutable reference to the underlying reader. - pub fn get_mut(&mut self) -> &mut R { - &mut self.inner - } - - /// Returns a reference to the internally buffered data. - /// - /// Unlike [`fill_buf`], this will not attempt to fill the buffer if it is empty. - /// - /// [`fill_buf`]: BufRead::fill_buf - pub fn buffer(&self) -> &[u8] { - &self.buf[self.pos..self.filled] - } - - /// Returns the number of bytes the internal buffer can hold at once. - pub const fn capacity(&self) -> usize { - DEFAULT_BUF_SIZE - } - - /// Unwraps this `BufReader`, returning the underlying reader. - pub fn into_inner(self) -> R { - self.inner - } - - fn discard_buffer(&mut self) { - self.pos = 0; - self.filled = 0; - } - - const fn is_empty(&self) -> bool { - self.pos >= self.filled - } -} - -impl Read for BufReader { - fn read(&mut self, buf: &mut [u8]) -> Result { - // If we don't have any buffered data and we're doing a massive read - // (larger than our internal buffer), bypass our internal buffer - // entirely. - if self.is_empty() && buf.len() >= self.capacity() { - self.discard_buffer(); - return self.inner.read(buf); - } - let nread = { - let mut rem = self.fill_buf()?; - rem.read(buf)? - }; - self.consume(nread); - Ok(nread) - } - - // Small read_exacts from a BufReader are extremely common when used with a deserializer. - // The default implementation calls read in a loop, which results in surprisingly poor code - // generation for the common path where the buffer has enough bytes to fill the passed-in - // buffer. - fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { - let amt = buf.len(); - if let Some(claimed) = self.buffer().get(..amt) { - buf.copy_from_slice(claimed); - self.pos += amt; - return Ok(()); - } - self.inner.read_exact(buf) - } - - // The inner reader might have an optimized `read_to_end`. Drain our buffer and then - // delegate to the inner implementation. - #[cfg(feature = "alloc")] - fn read_to_end(&mut self, buf: &mut Vec) -> Result { - let inner_buf = self.buffer(); - buf.extend_from_slice(inner_buf); - let nread = inner_buf.len(); - self.discard_buffer(); - Ok(nread + self.inner.read_to_end(buf)?) - } - - // The inner reader might have an optimized `read_to_end`. Drain our buffer and then - // delegate to the inner implementation. - #[cfg(feature = "alloc")] - fn read_to_string(&mut self, buf: &mut String) -> Result { - // In the general `else` case below we must read bytes into a side buffer, check - // that they are valid UTF-8, and then append them to `buf`. This requires a - // potentially large memcpy. - // - // If `buf` is empty--the most common case--we can leverage `append_to_string` - // to read directly into `buf`'s internal byte buffer, saving an allocation and - // a memcpy. - if buf.is_empty() { - // `append_to_string`'s safety relies on the buffer only being appended to since - // it only checks the UTF-8 validity of new data. If there were existing content in - // `buf` then an untrustworthy reader (i.e. `self.inner`) could not only append - // bytes but also modify existing bytes and render them invalid. On the other hand, - // if `buf` is empty then by definition any writes must be appends and - // `append_to_string` will validate all of the new bytes. - unsafe { crate::append_to_string(buf, |b| self.read_to_end(b)) } - } else { - // We cannot append our byte buffer directly onto the `buf` String as there could - // be an incomplete UTF-8 sequence that has only been partially read. We must read - // everything into a side buffer first and then call `from_utf8` on the complete - // buffer. - let mut bytes = Vec::new(); - self.read_to_end(&mut bytes)?; - let string = core::str::from_utf8(&bytes).map_err(|_| { - axerrno::ax_err_type!(InvalidData, "stream did not contain valid UTF-8") - })?; - *buf += string; - Ok(string.len()) - } - } -} - -impl BufRead for BufReader { - fn fill_buf(&mut self) -> Result<&[u8]> { - if self.is_empty() { - let read_len = self.inner.read(&mut self.buf)?; - self.pos = 0; - self.filled = read_len; - } - Ok(self.buffer()) - } - - fn consume(&mut self, amt: usize) { - self.pos = core::cmp::min(self.pos + amt, self.filled); - } -} diff --git a/src/buffered/bufreader/buffer.rs b/src/buffered/bufreader/buffer.rs new file mode 100644 index 0000000..641e9ea --- /dev/null +++ b/src/buffered/bufreader/buffer.rs @@ -0,0 +1,177 @@ +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +use core::{cmp, io::BorrowedBuf, mem::MaybeUninit}; + +#[cfg(not(feature = "alloc"))] +use heapless::Vec; + +#[cfg(not(feature = "alloc"))] +use crate::DEFAULT_BUF_SIZE; +use crate::{Read, Result}; + +pub struct Buffer { + // The buffer. + #[cfg(feature = "alloc")] + buf: Box<[MaybeUninit]>, + // The buffer. + #[cfg(not(feature = "alloc"))] + buf: Vec, DEFAULT_BUF_SIZE, u16>, + + // The current seek offset into `buf`, must always be <= `filled`. + pos: usize, + // Each call to `fill_buf` sets `filled` to indicate how many bytes at the start of `buf` are + // initialized with bytes from a read. + filled: usize, + #[cfg(borrowedbuf_init)] + // This is the max number of bytes returned across all `fill_buf` calls. We track this so that + // we can accurately tell `read_buf` how many bytes of buf are initialized, to bypass as much + // of its defensive initialization as possible. Note that while this often the same as + // `filled`, it doesn't need to be. Calls to `fill_buf` are not required to actually fill the + // buffer, and omitting this is a huge perf regression for `Read` impls that do not. + initialized: usize, +} + +impl Buffer { + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + #[cfg(feature = "alloc")] + let buf = Box::new_uninit_slice(capacity); + #[cfg(not(feature = "alloc"))] + let buf = { + let mut buf = Vec::new(); + assert!(capacity <= buf.capacity()); + unsafe { buf.set_len(capacity) }; + buf + }; + Self { + buf, + pos: 0, + filled: 0, + #[cfg(borrowedbuf_init)] + initialized: 0, + } + } + + #[inline] + pub fn buffer(&self) -> &[u8] { + // SAFETY: self.pos and self.cap are valid, and self.cap => 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 { + self.buf.len() + } + + #[inline] + pub fn filled(&self) -> usize { + self.filled + } + + #[inline] + pub fn pos(&self) -> usize { + self.pos + } + + #[cfg(borrowedbuf_init)] + #[inline] + pub fn initialized(&self) -> usize { + self.initialized + } + + #[inline] + pub fn discard_buffer(&mut self) { + self.pos = 0; + self.filled = 0; + } + + #[inline] + pub 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 + where + V: FnMut(&[u8]), + { + if let Some(claimed) = self.buffer().get(..amt) { + visitor(claimed); + // If the indexing into self.buffer() succeeds, amt must be a valid increment. + self.pos += amt; + true + } else { + false + } + } + + #[inline] + pub 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) -> Result { + let mut buf = BorrowedBuf::from(&mut self.buf[self.filled..]); + #[cfg(borrowedbuf_init)] + let old_init = self.initialized - self.filled; + #[cfg(borrowedbuf_init)] + unsafe { + buf.set_init(old_init); + } + reader.read_buf(buf.unfilled())?; + self.filled += buf.len(); + #[cfg(borrowedbuf_init)] + { + self.initialized += buf.init_len() - old_init; + } + Ok(buf.len()) + } + + /// Remove bytes that have already been read from the buffer. + pub fn backshift(&mut self) { + self.buf.copy_within(self.pos.., 0); + self.filled -= self.pos; + self.pos = 0; + } + + #[inline] + pub fn fill_buf(&mut self, mut reader: impl Read) -> 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 `==` + // to tell the compiler that the pos..cap slice is always valid. + if self.pos >= self.filled { + debug_assert!(self.pos == self.filled); + + #[cfg(feature = "alloc")] + let mut buf = BorrowedBuf::from(&mut *self.buf); + #[cfg(not(feature = "alloc"))] + let mut buf = BorrowedBuf::from(self.buf.as_mut_slice()); + #[cfg(borrowedbuf_init)] + // SAFETY: `self.filled` bytes will always have been initialized. + unsafe { + buf.set_init(self.initialized); + } + + let result = reader.read_buf(buf.unfilled()); + + self.pos = 0; + self.filled = buf.len(); + #[cfg(borrowedbuf_init)] + { + self.initialized = buf.init_len(); + } + + result?; + } + Ok(self.buffer()) + } +} diff --git a/src/buffered/bufreader/mod.rs b/src/buffered/bufreader/mod.rs new file mode 100644 index 0000000..130fb28 --- /dev/null +++ b/src/buffered/bufreader/mod.rs @@ -0,0 +1,350 @@ +mod buffer; + +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; +use core::{fmt, io::BorrowedCursor}; + +use self::buffer::Buffer; +#[cfg(feature = "alloc")] +use crate::Error; +use crate::{BufRead, DEFAULT_BUF_SIZE, Read, Result, Seek, SeekFrom}; + +/// The `BufReader` struct adds buffering to any reader. +/// +/// See [`std::io::BufReader`] for more details. +pub struct BufReader { + buf: Buffer, + inner: R, +} + +impl BufReader { + /// Creates a new `BufReader` with the specified buffer capacity. + pub fn with_capacity(capacity: usize, inner: R) -> BufReader { + BufReader { + buf: Buffer::with_capacity(capacity), + inner, + } + } + + /// Creates a new `BufReader` with a default buffer capacity. + pub fn new(inner: R) -> BufReader { + BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) + } +} + +impl BufReader { + /// Gets a reference to the underlying reader. + /// + /// It is inadvisable to directly read from the underlying reader. + pub fn get_ref(&self) -> &R { + &self.inner + } + + /// Gets a mutable reference to the underlying reader. + /// + /// It is inadvisable to directly read from the underlying reader. + pub fn get_mut(&mut self) -> &mut R { + &mut self.inner + } + + /// Returns a reference to the internally buffered data. + /// + /// Unlike [`fill_buf`], this will not attempt to fill the buffer if it is empty. + /// + /// [`fill_buf`]: BufRead::fill_buf + pub fn buffer(&self) -> &[u8] { + self.buf.buffer() + } + + /// Returns the number of bytes the internal buffer can hold at once. + pub fn capacity(&self) -> usize { + self.buf.capacity() + } + + #[cfg(borrowedbuf_init)] + #[doc(hidden)] + pub fn initialized(&self) -> usize { + self.buf.initialized() + } + + /// Unwraps this `BufReader`, returning the underlying reader. + /// + /// Note that any leftover data in the internal buffer is lost. Therefore, + /// a following read from the underlying reader may lead to data loss. + pub fn into_inner(self) -> R + where + R: Sized, + { + self.inner + } + + /// Invalidates all data in the internal buffer. + #[inline] + pub(crate) fn discard_buffer(&mut self) { + self.buf.discard_buffer() + } +} + +impl BufReader { + /// Attempt to look ahead `n` bytes. + /// + /// `n` must be less than or equal to `capacity`. + /// + /// The returned slice may be less than `n` bytes long if + /// end of file is reached. + /// + /// After calling this method, you may call [`consume`](BufRead::consume) + /// with a value less than or equal to `n` to advance over some or all of + /// the returned bytes. + pub fn peek(&mut self, n: usize) -> Result<&[u8]> { + assert!(n <= self.capacity()); + while n > self.buf.buffer().len() { + if self.buf.pos() > 0 { + self.buf.backshift(); + } + let new = self.buf.read_more(&mut self.inner)?; + if new == 0 { + // end of file, no more bytes to read + return Ok(self.buf.buffer()); + } + debug_assert_eq!(self.buf.pos(), 0); + } + Ok(&self.buf.buffer()[..n]) + } +} + +impl fmt::Debug for BufReader +where + R: ?Sized + fmt::Debug, +{ + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("BufReader") + .field("reader", &&self.inner) + .field( + "buffer", + &format_args!("{}/{}", self.buf.filled() - self.buf.pos(), self.capacity()), + ) + .finish() + } +} + +impl Read for BufReader { + fn read(&mut self, buf: &mut [u8]) -> Result { + // If we don't have any buffered data and we're doing a massive read + // (larger than our internal buffer), bypass our internal buffer + // entirely. + if self.buf.pos() == self.buf.filled() && buf.len() >= self.capacity() { + self.discard_buffer(); + return self.inner.read(buf); + } + let mut rem = self.fill_buf()?; + let nread = rem.read(buf)?; + self.consume(nread); + Ok(nread) + } + + fn read_buf(&mut self, mut cursor: BorrowedCursor<'_>) -> Result<()> { + // If we don't have any buffered data and we're doing a massive read + // (larger than our internal buffer), bypass our internal buffer + // entirely. + if self.buf.pos() == self.buf.filled() && cursor.capacity() >= self.capacity() { + self.discard_buffer(); + return self.inner.read_buf(cursor); + } + + let prev = cursor.written(); + + let mut rem = self.fill_buf()?; + rem.read_buf(cursor.reborrow())?; // actually never fails + + self.consume(cursor.written() - prev); // slice impl of read_buf known to never unfill buf + + Ok(()) + } + + // Small read_exacts from a BufReader are extremely common when used with a deserializer. + // The default implementation calls read in a loop, which results in surprisingly poor code + // generation for the common path where the buffer has enough bytes to fill the passed-in + // buffer. + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + if self + .buf + .consume_with(buf.len(), |claimed| buf.copy_from_slice(claimed)) + { + return Ok(()); + } + + crate::default_read_exact(self, buf) + } + + fn read_buf_exact(&mut self, mut cursor: BorrowedCursor<'_>) -> Result<()> { + if self + .buf + .consume_with(cursor.capacity(), |claimed| cursor.append(claimed)) + { + return Ok(()); + } + + crate::default_read_buf_exact(self, cursor) + } + + // The inner reader might have an optimized `read_to_end`. Drain our buffer and then + // delegate to the inner implementation. + #[cfg(feature = "alloc")] + fn read_to_end(&mut self, buf: &mut Vec) -> Result { + let inner_buf = self.buffer(); + buf.try_reserve(inner_buf.len()) + .map_err(|_| Error::NoMemory)?; + buf.extend_from_slice(inner_buf); + let nread = inner_buf.len(); + self.discard_buffer(); + Ok(nread + self.inner.read_to_end(buf)?) + } + + // The inner reader might have an optimized `read_to_end`. Drain our buffer and then + // delegate to the inner implementation. + #[cfg(feature = "alloc")] + fn read_to_string(&mut self, buf: &mut String) -> Result { + // In the general `else` case below we must read bytes into a side buffer, check + // that they are valid UTF-8, and then append them to `buf`. This requires a + // potentially large memcpy. + // + // If `buf` is empty--the most common case--we can leverage `append_to_string` + // to read directly into `buf`'s internal byte buffer, saving an allocation and + // a memcpy. + + if buf.is_empty() { + // `append_to_string`'s safety relies on the buffer only being appended to since + // it only checks the UTF-8 validity of new data. If there were existing content in + // `buf` then an untrustworthy reader (i.e. `self.inner`) could not only append + // bytes but also modify existing bytes and render them invalid. On the other hand, + // if `buf` is empty then by definition any writes must be appends and + // `append_to_string` will validate all of the new bytes. + unsafe { crate::append_to_string(buf, |b| self.read_to_end(b)) } + } else { + // We cannot append our byte buffer directly onto the `buf` String as there could + // be an incomplete UTF-8 sequence that has only been partially read. We must read + // everything into a side buffer first and then call `from_utf8` on the complete + // buffer. + let mut bytes = Vec::new(); + self.read_to_end(&mut bytes)?; + let string = str::from_utf8(&bytes).map_err(|_| Error::IllegalBytes)?; + *buf += string; + Ok(string.len()) + } + } +} + +impl BufRead for BufReader { + fn fill_buf(&mut self) -> Result<&[u8]> { + self.buf.fill_buf(&mut self.inner) + } + + fn consume(&mut self, amt: usize) { + self.buf.consume(amt) + } +} + +impl Seek for BufReader { + /// Seek to an offset, in bytes, in the underlying reader. + /// + /// The position used for seeking with [SeekFrom::Current]\(_) is the + /// position the underlying reader would be at if the `BufReader` had no + /// internal buffer. + /// + /// Seeking always discards the internal buffer, even if the seek position + /// would otherwise fall within it. This guarantees that calling + /// [`BufReader::into_inner()`] immediately after a seek yields the underlying reader + /// at the same position. + /// + /// To seek without discarding the internal buffer, use [`BufReader::seek_relative`]. + /// + /// See [`Seek`] for more details. + /// + /// Note: In the edge case where you're seeking with [SeekFrom::Current]\(n) + /// where `n` minus the internal buffer length overflows an `i64`, two + /// seeks will be performed instead of one. If the second seek returns + /// [`Err`], the underlying reader will be left at the same position it would + /// have if you called `seek` with [SeekFrom::Current]\(0). + fn seek(&mut self, pos: SeekFrom) -> Result { + let result: u64; + if let SeekFrom::Current(n) = pos { + let remainder = (self.buf.filled() - self.buf.pos()) as i64; + // it should be safe to assume that remainder fits within an i64 as the alternative + // means we managed to allocate 8 exbibytes and that's absurd. + // But it's not out of the realm of possibility for some weird underlying reader to + // support seeking by i64::MIN so we need to handle underflow when subtracting + // remainder. + if let Some(offset) = n.checked_sub(remainder) { + result = self.inner.seek(SeekFrom::Current(offset))?; + } else { + // seek backwards by our remainder, and then by the offset + self.inner.seek(SeekFrom::Current(-remainder))?; + self.discard_buffer(); + result = self.inner.seek(SeekFrom::Current(n))?; + } + } else { + // Seeking with Start/End doesn't care about our buffer length. + result = self.inner.seek(pos)?; + } + self.discard_buffer(); + Ok(result) + } + + /// Returns the current seek position from the start of the stream. + /// + /// The value returned is equivalent to `self.seek(SeekFrom::Current(0))` + /// but does not flush the internal buffer. Due to this optimization the + /// function does not guarantee that calling `.into_inner()` immediately + /// afterwards will yield the underlying reader at the same position. Use + /// [`BufReader::seek`] instead if you require that guarantee. + /// + /// # Panics + /// + /// This function will panic if the position of the inner reader is smaller + /// than the amount of buffered data. That can happen if the inner reader + /// has an incorrect implementation of [`Seek::stream_position`], or if the + /// position has gone out of sync due to calling [`Seek::seek`] directly on + /// the underlying reader. + fn stream_position(&mut self) -> Result { + let remainder = (self.buf.filled() - self.buf.pos()) as u64; + self.inner.stream_position().map(|pos| { + pos.checked_sub(remainder).expect( + "overflow when subtracting remaining buffer size from inner stream position", + ) + }) + } + + /// Seeks relative to the current position. + /// + /// If the new position lies within the buffer, the buffer will not be + /// flushed, allowing for more efficient seeks. This method does not return + /// the location of the underlying reader, so the caller must track this + /// information themselves if it is required. + fn seek_relative(&mut self, offset: i64) -> Result<()> { + self.seek_relative(offset) + } +} + +impl BufReader { + /// Seeks relative to the current position. If the new position lies within the buffer, + /// the buffer will not be flushed, allowing for more efficient seeks. + /// This method does not return the location of the underlying reader, so the caller + /// must track this information themselves if it is required. + pub fn seek_relative(&mut self, offset: i64) -> Result<()> { + let pos = self.buf.pos() as u64; + if offset < 0 { + if pos.checked_sub((-offset) as u64).is_some() { + self.buf.unconsume((-offset) as usize); + return Ok(()); + } + } else if let Some(new_pos) = pos.checked_add(offset as u64) + && new_pos <= self.buf.filled() as u64 + { + self.buf.consume(offset as usize); + return Ok(()); + } + + self.seek(SeekFrom::Current(offset)).map(drop) + } +} From d0dc83a36f1b2a5f86852b8d3055b71c2a7e7242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Wed, 24 Dec 2025 16:41:09 +0800 Subject: [PATCH 2/6] feat: probe maybe_uninit_slice --- build.rs | 14 +++++++++++++- src/lib.rs | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index f664cf4..e5eb130 100644 --- a/build.rs +++ b/build.rs @@ -1,10 +1,11 @@ use std::{env, fs, path::PathBuf}; fn main() { - autocfg::emit_possibility("borrowedbuf_init"); autocfg::rerun_path("build.rs"); let ac = autocfg::new(); + + autocfg::emit_possibility("borrowedbuf_init"); let code = r#" #![no_std] #![feature(core_io_borrowed_buf)] @@ -16,6 +17,17 @@ fn main() { autocfg::emit("borrowedbuf_init"); } + autocfg::emit_possibility("maybe_uninit_slice"); + let code = r#" + #![no_std] + pub fn probe() { + let _ = <[core::mem::MaybeUninit<()>]>::assume_init_mut; + } + "#; + if ac.probe_raw(code).is_ok() { + autocfg::emit("maybe_uninit_slice"); + } + let buf_size = env::var("AXIO_DEFAULT_BUF_SIZE") .map(|v| v.parse::().expect("Invalid AXIO_DEFAULT_BUF_SIZE")) .unwrap_or(1024 * 2); diff --git a/src/lib.rs b/src/lib.rs index 8a51517..9505d0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![feature(doc_cfg)] #![feature(core_io_borrowed_buf)] #![cfg_attr(not(borrowedbuf_init), feature(maybe_uninit_fill))] +#![cfg_attr(not(maybe_uninit_slice), feature(maybe_uninit_slice))] #![warn(missing_docs)] #[cfg(feature = "alloc")] From 45e888149565e55fa92ae39e9adfd22530f7d9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Wed, 24 Dec 2025 16:45:04 +0800 Subject: [PATCH 3/6] feat: add BufWriter and LineWriter --- src/buffered/bufwriter/mod.rs | 386 ++++++++++++++++++++++++++++++++ src/buffered/linewriter/mod.rs | 103 +++++++++ src/buffered/linewriter/shim.rs | 178 +++++++++++++++ src/buffered/mod.rs | 75 ++++++- 4 files changed, 741 insertions(+), 1 deletion(-) create mode 100644 src/buffered/bufwriter/mod.rs create mode 100644 src/buffered/linewriter/mod.rs create mode 100644 src/buffered/linewriter/shim.rs diff --git a/src/buffered/bufwriter/mod.rs b/src/buffered/bufwriter/mod.rs new file mode 100644 index 0000000..2c09420 --- /dev/null +++ b/src/buffered/bufwriter/mod.rs @@ -0,0 +1,386 @@ +use core::{fmt, mem::ManuallyDrop, ptr}; + +use crate::{DEFAULT_BUF_SIZE, Error, IntoInnerError, Result, Seek, SeekFrom, Write}; + +#[cfg(feature = "alloc")] +type Buffer = alloc::vec::Vec; +#[cfg(not(feature = "alloc"))] +type Buffer = heapless::Vec; + +/// Wraps a writer and buffers its output. +/// +/// See [std::io::BufWriter] for more details. +pub struct BufWriter { + // The buffer. + buf: Buffer, + // #30888: If the inner writer panics in a call to write, we don't want to + // write the buffered data a second time in BufWriter's destructor. This + // flag tells the Drop impl if it should skip the flush. + panicked: bool, + inner: W, +} + +impl BufWriter { + /// Creates a new `BufWriter` with a default buffer capacity. + pub fn new(inner: W) -> BufWriter { + #[cfg(feature = "alloc")] + let buf = Buffer::with_capacity(DEFAULT_BUF_SIZE); + #[cfg(not(feature = "alloc"))] + let buf = Buffer::new(); + BufWriter { + buf, + panicked: false, + inner, + } + } + + /// Creates a new `BufWriter` with at least the specified buffer capacity. + #[cfg(feature = "alloc")] + pub fn with_capacity(capacity: usize, inner: W) -> BufWriter { + BufWriter { + buf: Buffer::with_capacity(capacity), + panicked: false, + inner, + } + } + + /// Unwraps this `BufWriter`, returning the underlying writer. + /// + /// The buffer is written out before returning the writer. + /// + /// # Errors + /// + /// An [`Err`] will be returned if an error occurs while flushing the buffer. + #[cfg_attr(not(feature = "alloc"), allow(clippy::result_large_err))] + pub fn into_inner(mut self) -> core::result::Result>> { + match self.flush_buf() { + Err(e) => Err(IntoInnerError::new(self, e)), + Ok(()) => Ok(self.into_parts().0), + } + } + + /// Disassembles this `BufWriter`, returning the underlying writer, and any buffered but + /// unwritten data. + /// + /// If the underlying writer panicked, it is not known what portion of the data was written. + /// In this case, we return `WriterPanicked` for the buffered data (from which the buffer + /// contents can still be recovered). + /// + /// `into_parts` makes no attempt to flush data and cannot fail. + pub fn into_parts(self) -> (W, core::result::Result) { + let mut this = ManuallyDrop::new(self); + let buf = core::mem::take(&mut this.buf); + let buf = if !this.panicked { + Ok(buf) + } else { + Err(WriterPanicked { buf }) + }; + + // SAFETY: double-drops are prevented by putting `this` in a ManuallyDrop that is never + // dropped + let inner = unsafe { ptr::read(&this.inner) }; + + (inner, buf) + } +} + +/// Error returned for the buffered data from `BufWriter::into_parts`, when the underlying +/// writer has previously panicked. Contains the (possibly partly written) buffered data. +pub struct WriterPanicked { + buf: Buffer, +} + +impl WriterPanicked { + /// Returns the perhaps-unwritten data. Some of this data may have been written by the + /// panicking call(s) to the underlying writer, so simply writing it again is not a good idea. + #[must_use = "`self` will be dropped if the result is not used"] + pub fn into_inner(self) -> Buffer { + self.buf + } +} + +impl fmt::Display for WriterPanicked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "BufWriter inner writer panicked, what data remains unwritten is not known".fmt(f) + } +} + +impl fmt::Debug for WriterPanicked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WriterPanicked") + .field( + "buffer", + &format_args!("{}/{}", self.buf.len(), self.buf.capacity()), + ) + .finish() + } +} + +impl BufWriter { + /// Gets a reference to the underlying writer. + pub fn get_ref(&self) -> &W { + &self.inner + } + + /// Gets a mutable reference to the underlying writer. + /// + /// It is inadvisable to directly write to the underlying writer. + pub fn get_mut(&mut self) -> &mut W { + &mut self.inner + } + + /// Returns a reference to the internally buffered data. + pub fn buffer(&self) -> &[u8] { + self.buf.as_slice() + } + + /// Returns the number of bytes the internal buffer can hold without flushing. + pub fn capacity(&self) -> usize { + self.buf.capacity() + } + + /// Send data in our local buffer into the inner writer, looping as + /// necessary until either it's all been sent or an error occurs. + /// + /// Because all the data in the buffer has been reported to our owner as + /// "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(crate) fn flush_buf(&mut self) -> Result<()> { + /// Helper struct to ensure the buffer is updated after all the writes + /// are complete. It tracks the number of written bytes and drains them + /// all from the front of the buffer when dropped. + struct BufGuard<'a> { + buffer: &'a mut Buffer, + written: usize, + } + + impl<'a> BufGuard<'a> { + fn new(buffer: &'a mut Buffer) -> Self { + Self { buffer, written: 0 } + } + + /// The unwritten part of the buffer + fn remaining(&self) -> &[u8] { + &self.buffer.as_slice()[self.written..] + } + + /// Flag some bytes as removed from the front of the buffer + fn consume(&mut self, amt: usize) { + self.written += amt; + } + + /// true if all of the bytes have been written + fn done(&self) -> bool { + self.written >= self.buffer.len() + } + } + + impl Drop for BufGuard<'_> { + fn drop(&mut self) { + if self.written > 0 { + self.buffer.drain(..self.written); + } + } + } + + let mut guard = BufGuard::new(&mut self.buf); + while !guard.done() { + self.panicked = true; + let r = self.inner.write(guard.remaining()); + self.panicked = false; + + match r { + Ok(0) => { + return Err(Error::WriteZero); + } + Ok(n) => guard.consume(n), + Err(ref e) if e.canonicalize() == Error::Interrupted => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn spare_capacity(&self) -> usize { + self.buf.capacity() - self.buf.len() + } + + // SAFETY: Requires `buf.len() <= self.buf.capacity() - self.buf.len()`, + // i.e., that input buffer length is less than or equal to spare capacity. + #[inline] + unsafe fn write_to_buffer_unchecked(&mut self, buf: &[u8]) { + debug_assert!(buf.len() <= self.spare_capacity()); + let old_len = self.buf.len(); + let buf_len = buf.len(); + let src = buf.as_ptr(); + unsafe { + let dst = self.buf.as_mut_ptr().add(old_len); + core::ptr::copy_nonoverlapping(src, dst, buf_len); + self.buf.set_len(old_len + buf_len); + } + } + + /// Buffer some data without flushing it, regardless of the size of the + /// data. Writes as much as possible without exceeding capacity. Returns + /// the number of bytes written. + pub(crate) fn write_to_buf(&mut self, buf: &[u8]) -> usize { + let available = self.spare_capacity(); + let amt_to_buffer = available.min(buf.len()); + + // SAFETY: `amt_to_buffer` is <= buffer's spare capacity by construction. + unsafe { + self.write_to_buffer_unchecked(&buf[..amt_to_buffer]); + } + + amt_to_buffer + } + + // Ensure this function does not get inlined into `write`, so that it + // remains inlineable and its common path remains as short as possible. + // If this function ends up being called frequently relative to `write`, + // it's likely a sign that the client is using an improperly sized buffer + // or their write patterns are somewhat pathological. + #[cold] + #[inline(never)] + fn write_cold(&mut self, buf: &[u8]) -> Result { + if buf.len() > self.spare_capacity() { + self.flush_buf()?; + } + + // Why not len > capacity? To avoid a needless trip through the buffer when the input + // exactly fills it. We'd just need to flush it to the underlying writer anyway. + if buf.len() >= self.buf.capacity() { + self.panicked = true; + let r = self.get_mut().write(buf); + self.panicked = false; + r + } else { + // Write to the buffer. In this case, we write to the buffer even if it fills it + // exactly. Doing otherwise would mean flushing the buffer, then writing this + // input to the inner writer, which in many cases would be a worse strategy. + + // SAFETY: There was either enough spare capacity already, or there wasn't and we + // flushed the buffer to ensure that there is. In the latter case, we know that there + // is because flushing ensured that our entire buffer is spare capacity, and we entered + // this block because the input buffer length is less than that capacity. In either + // case, it's safe to write the input buffer to our buffer. + unsafe { + self.write_to_buffer_unchecked(buf); + } + + Ok(buf.len()) + } + } + + // Ensure this function does not get inlined into `write_all`, so that it + // remains inlineable and its common path remains as short as possible. + // If this function ends up being called frequently relative to `write_all`, + // it's likely a sign that the client is using an improperly sized buffer + // or their write patterns are somewhat pathological. + #[cold] + #[inline(never)] + fn write_all_cold(&mut self, buf: &[u8]) -> Result<()> { + // Normally, `write_all` just calls `write` in a loop. We can do better + // by calling `self.get_mut().write_all()` directly, which avoids + // round trips through the buffer in the event of a series of partial + // writes in some circumstances. + + if buf.len() > self.spare_capacity() { + self.flush_buf()?; + } + + // Why not len > capacity? To avoid a needless trip through the buffer when the input + // exactly fills it. We'd just need to flush it to the underlying writer anyway. + if buf.len() >= self.buf.capacity() { + self.panicked = true; + let r = self.get_mut().write_all(buf); + self.panicked = false; + r + } else { + // Write to the buffer. In this case, we write to the buffer even if it fills it + // exactly. Doing otherwise would mean flushing the buffer, then writing this + // input to the inner writer, which in many cases would be a worse strategy. + + // SAFETY: There was either enough spare capacity already, or there wasn't and we + // flushed the buffer to ensure that there is. In the latter case, we know that there + // is because flushing ensured that our entire buffer is spare capacity, and we entered + // this block because the input buffer length is less than that capacity. In either + // case, it's safe to write the input buffer to our buffer. + unsafe { + self.write_to_buffer_unchecked(buf); + } + + Ok(()) + } + } +} + +impl fmt::Debug for BufWriter { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("BufWriter") + .field("writer", &&self.inner) + .field( + "buffer", + &format_args!("{}/{}", self.buf.len(), self.buf.capacity()), + ) + .finish() + } +} + +impl Write for BufWriter { + #[inline] + fn write(&mut self, buf: &[u8]) -> Result { + // Use < instead of <= to avoid a needless trip through the buffer in some cases. + // See `write_cold` for details. + if buf.len() < self.spare_capacity() { + // SAFETY: safe by above conditional. + unsafe { + self.write_to_buffer_unchecked(buf); + } + + Ok(buf.len()) + } else { + self.write_cold(buf) + } + } + + #[inline] + fn write_all(&mut self, buf: &[u8]) -> Result<()> { + // Use < instead of <= to avoid a needless trip through the buffer in some cases. + // See `write_all_cold` for details. + if buf.len() < self.spare_capacity() { + // SAFETY: safe by above conditional. + unsafe { + self.write_to_buffer_unchecked(buf); + } + + Ok(()) + } else { + self.write_all_cold(buf) + } + } + + fn flush(&mut self) -> Result<()> { + self.flush_buf().and_then(|()| self.get_mut().flush()) + } +} + +impl Seek for BufWriter { + /// Seek to the offset, in bytes, in the underlying writer. + /// + /// Seeking always writes out the internal buffer before seeking. + fn seek(&mut self, pos: SeekFrom) -> Result { + self.flush_buf()?; + self.get_mut().seek(pos) + } +} + +impl Drop for BufWriter { + fn drop(&mut self) { + if !self.panicked { + // dtors should not panic, so we ignore a failed flush + let _r = self.flush_buf(); + } + } +} diff --git a/src/buffered/linewriter/mod.rs b/src/buffered/linewriter/mod.rs new file mode 100644 index 0000000..032ae0c --- /dev/null +++ b/src/buffered/linewriter/mod.rs @@ -0,0 +1,103 @@ +mod shim; + +use core::fmt; + +use self::shim::LineWriterShim; +use crate::{BufWriter, IntoInnerError, Result, Write}; + +/// Wraps a writer and buffers output to it, flushing whenever a newline +/// (`0x0a`, `'\n'`) is detected. +/// +/// The [`BufWriter`] struct wraps a writer and buffers its output. +/// But it only does this batched write when it goes out of scope, or when the +/// internal buffer is full. Sometimes, you'd prefer to write each line as it's +/// completed, rather than the entire buffer at once. Enter `LineWriter`. It +/// does exactly that. +/// +/// Like [`BufWriter`], a `LineWriter`’s buffer will also be flushed when the +/// `LineWriter` goes out of scope or when its internal buffer is full. +/// +/// If there's still a partial line in the buffer when the `LineWriter` is +/// dropped, it will flush those contents. +/// +/// See [`std::io::LineWriter`] for more details. +pub struct LineWriter { + inner: BufWriter, +} + +impl LineWriter { + /// Creates a new `LineWriter`. + pub fn new(inner: W) -> LineWriter { + LineWriter { + inner: BufWriter::new(inner), + } + } + + /// Creates a new `LineWriter` with at least the specified capacity for the + /// internal buffer. + #[cfg(feature = "alloc")] + pub fn with_capacity(capacity: usize, inner: W) -> LineWriter { + LineWriter { + inner: BufWriter::with_capacity(capacity, inner), + } + } + + /// Unwraps this `LineWriter`, returning the underlying writer. + /// + /// The internal buffer is written out before returning the writer. + /// + /// # Errors + /// + /// An [`Err`] will be returned if an error occurs while flushing the buffer. + #[cfg_attr(not(feature = "alloc"), allow(clippy::result_large_err))] + pub fn into_inner(self) -> core::result::Result>> { + self.inner + .into_inner() + .map_err(|err| err.new_wrapped(|inner| LineWriter { inner })) + } +} + +impl LineWriter { + /// Gets a reference to the underlying writer. + pub fn get_ref(&self) -> &W { + self.inner.get_ref() + } + + /// Gets a mutable reference to the underlying writer. + /// + /// Caution must be taken when calling methods on the mutable reference + /// returned as extra writes could corrupt the output stream. + pub fn get_mut(&mut self) -> &mut W { + self.inner.get_mut() + } +} + +impl Write for LineWriter { + fn write(&mut self, buf: &[u8]) -> Result { + LineWriterShim::new(&mut self.inner).write(buf) + } + + fn flush(&mut self) -> Result<()> { + self.inner.flush() + } + + fn write_all(&mut self, buf: &[u8]) -> Result<()> { + LineWriterShim::new(&mut self.inner).write_all(buf) + } + + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> { + LineWriterShim::new(&mut self.inner).write_fmt(fmt) + } +} + +impl fmt::Debug for LineWriter { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("LineWriter") + .field("writer", &self.get_ref()) + .field( + "buffer", + &format_args!("{}/{}", self.inner.buffer().len(), self.inner.capacity()), + ) + .finish_non_exhaustive() + } +} diff --git a/src/buffered/linewriter/shim.rs b/src/buffered/linewriter/shim.rs new file mode 100644 index 0000000..eb1fd98 --- /dev/null +++ b/src/buffered/linewriter/shim.rs @@ -0,0 +1,178 @@ +use crate::{BufWriter, Result, Write}; + +/// Private helper struct for implementing the line-buffered writing logic. +/// +/// This shim temporarily wraps a BufWriter, and uses its internals to +/// implement a line-buffered writer (specifically by using the internal +/// methods like write_to_buf and flush_buf). In this way, a more +/// efficient abstraction can be created than one that only had access to +/// `write` and `flush`, without needlessly duplicating a lot of the +/// implementation details of BufWriter. This also allows existing +/// `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> { + buffer: &'a mut BufWriter, +} + +impl<'a, W: ?Sized + Write> LineWriterShim<'a, W> { + pub fn new(buffer: &'a mut BufWriter) -> Self { + Self { buffer } + } + + /// Gets a mutable reference to the inner writer (that is, the writer + /// wrapped by the BufWriter). Be careful with this writer, as writes to + /// it will bypass the buffer. + fn inner_mut(&mut self) -> &mut W { + self.buffer.get_mut() + } + + /// Gets the content currently buffered in self.buffer + fn buffered(&self) -> &[u8] { + self.buffer.buffer() + } + + /// Flushes the buffer iff the last byte is a newline (indicating that an + /// earlier write only succeeded partially, and we want to retry flushing + /// the buffered line before continuing with a subsequent write). + fn flush_if_completed_line(&mut self) -> Result<()> { + match self.buffered().last().copied() { + Some(b'\n') => self.buffer.flush_buf(), + _ => Ok(()), + } + } +} + +impl<'a, W: ?Sized + Write> Write for LineWriterShim<'a, W> { + /// Writes some data into this BufReader with line buffering. + /// + /// This means that, if any newlines are present in the data, the data up to + /// the last newline is sent directly to the underlying writer, and data + /// after it is buffered. Returns the number of bytes written. + /// + /// This function operates on a "best effort basis"; in keeping with the + /// convention of `Write::write`, it makes at most one attempt to write + /// new data to the underlying writer. If that write only reports a partial + /// success, the remaining data will be buffered. + /// + /// Because this function attempts to send completed lines to the underlying + /// writer, it will also flush the existing buffer if it ends with a + /// newline, even if the incoming data does not contain any newlines. + fn write(&mut self, buf: &[u8]) -> Result { + let newline_idx = match memchr::memrchr(b'\n', buf) { + // If there are no new newlines (that is, if this write is less than + // one line), just do a regular buffered write (which may flush if + // we exceed the inner buffer's size) + None => { + self.flush_if_completed_line()?; + return self.buffer.write(buf); + } + // Otherwise, arrange for the lines to be written directly to the + // inner writer. + Some(newline_idx) => newline_idx + 1, + }; + + // Flush existing content to prepare for our write. We have to do this + // before attempting to write `buf` in order to maintain consistency; + // if we add `buf` to the buffer then try to flush it all at once, + // we're obligated to return Ok(), which would mean suppressing any + // errors that occur during flush. + self.buffer.flush_buf()?; + + // This is what we're going to try to write directly to the inner + // writer. The rest will be buffered, if nothing goes wrong. + let lines = &buf[..newline_idx]; + + // Write `lines` directly to the inner writer. In keeping with the + // `write` convention, make at most one attempt to add new (unbuffered) + // data. Because this write doesn't touch the BufWriter state directly, + // and the buffer is known to be empty, we don't need to worry about + // self.buffer.panicked here. + let flushed = self.inner_mut().write(lines)?; + + // If buffer returns Ok(0), propagate that to the caller without + // doing additional buffering; otherwise we're just guaranteeing + // an "ErrorKind::WriteZero" later. + if flushed == 0 { + return Ok(0); + } + + // Now that the write has succeeded, buffer the rest (or as much of + // the rest as possible). If there were any unwritten newlines, we + // only buffer out to the last unwritten newline that fits in the + // buffer; this helps prevent flushing partial lines on subsequent + // calls to LineWriterShim::write. + + // Handle the cases in order of most-common to least-common, under + // the presumption that most writes succeed in totality, and that most + // writes are smaller than the buffer. + // - Is this a partial line (ie, no newlines left in the unwritten tail) + // - If not, does the data out to the last unwritten newline fit in the buffer? + // - If not, scan for the last newline that *does* fit in the buffer + let tail = if flushed >= newline_idx { + let tail = &buf[flushed..]; + // Avoid unnecessary short writes by not splitting the remaining + // bytes if they're larger than the buffer. + // They can be written in full by the next call to write. + if tail.len() >= self.buffer.capacity() { + return Ok(flushed); + } + tail + } else if newline_idx - flushed <= self.buffer.capacity() { + &buf[flushed..newline_idx] + } else { + let scan_area = &buf[flushed..]; + let scan_area = &scan_area[..self.buffer.capacity()]; + match memchr::memrchr(b'\n', scan_area) { + Some(newline_idx) => &scan_area[..newline_idx + 1], + None => scan_area, + } + }; + + let buffered = self.buffer.write_to_buf(tail); + Ok(flushed + buffered) + } + + fn flush(&mut self) -> Result<()> { + self.buffer.flush() + } + + /// Writes some data into this BufReader with line buffering. + /// + /// This means that, if any newlines are present in the data, the data up to + /// the last newline is sent directly to the underlying writer, and data + /// after it is buffered. + /// + /// Because this function attempts to send completed lines to the underlying + /// writer, it will also flush the existing buffer if it contains any + /// newlines, even if the incoming data does not contain any newlines. + fn write_all(&mut self, buf: &[u8]) -> Result<()> { + match memchr::memrchr(b'\n', buf) { + // If there are no new newlines (that is, if this write is less than + // one line), just do a regular buffered write (which may flush if + // we exceed the inner buffer's size) + None => { + self.flush_if_completed_line()?; + self.buffer.write_all(buf) + } + Some(newline_idx) => { + let (lines, tail) = buf.split_at(newline_idx + 1); + + if self.buffered().is_empty() { + self.inner_mut().write_all(lines)?; + } else { + // If there is any buffered data, we add the incoming lines + // to that buffer before flushing, which saves us at least + // one write call. We can't really do this with `write`, + // since we can't do this *and* not suppress errors *and* + // report a consistent state to the caller in a return + // value, but here in write_all it's fine. + self.buffer.write_all(lines)?; + self.buffer.flush_buf()?; + } + + self.buffer.write_all(tail) + } + } + } +} diff --git a/src/buffered/mod.rs b/src/buffered/mod.rs index c7dfe1e..d953053 100644 --- a/src/buffered/mod.rs +++ b/src/buffered/mod.rs @@ -1,3 +1,76 @@ mod bufreader; +mod bufwriter; +mod linewriter; -pub use self::bufreader::BufReader; +use core::fmt; + +pub use self::{ + bufreader::BufReader, + bufwriter::{BufWriter, WriterPanicked}, + linewriter::LineWriter, +}; +use crate::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. +#[derive(Debug)] +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. + 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. + 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. + 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. + pub fn into_parts(self) -> (Error, W) { + (self.1, self.0) + } +} + +impl From> for Error { + fn from(iie: IntoInnerError) -> Error { + iie.1 + } +} + +impl fmt::Display for IntoInnerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.error().fmt(f) + } +} From d4f79c6be99deb960db8229ebe00a3e9892a4703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Wed, 24 Dec 2025 16:46:24 +0800 Subject: [PATCH 4/6] docs: update README.md "Features" section for buffered --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 090f049..b3404d8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ - Enables extra methods on `Read`: `read_to_end`, `read_to_string`. - Enables extra methods on `BufRead`: `read_until`, `read_line`, `split`, `lines`. - Enables implementations of axio traits for `alloc` types like `Vec`, `Box`, etc. + - Enables `BufWriter::with_capacity`. (If `alloc` is disabled, only `BufWriter::new` is available.) + - Removes the capacity limit on `BufReader`. (If `alloc` is disabled, `BufReader::with_capacity` will panic if the capacity is larger than a fixed limit.) ### Differences to `std::io` From 275689da11f881e3c24c8f07e3a42ee3c4fd3c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Wed, 24 Dec 2025 16:53:22 +0800 Subject: [PATCH 5/6] chore: remove BufReader::seek_relative --- src/buffered/bufreader/mod.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/buffered/bufreader/mod.rs b/src/buffered/bufreader/mod.rs index 130fb28..2a26d61 100644 --- a/src/buffered/bufreader/mod.rs +++ b/src/buffered/bufreader/mod.rs @@ -322,16 +322,6 @@ impl Seek for BufReader { /// the location of the underlying reader, so the caller must track this /// information themselves if it is required. fn seek_relative(&mut self, offset: i64) -> Result<()> { - self.seek_relative(offset) - } -} - -impl BufReader { - /// Seeks relative to the current position. If the new position lies within the buffer, - /// the buffer will not be flushed, allowing for more efficient seeks. - /// This method does not return the location of the underlying reader, so the caller - /// must track this information themselves if it is required. - pub fn seek_relative(&mut self, offset: i64) -> Result<()> { let pos = self.buf.pos() as u64; if offset < 0 { if pos.checked_sub((-offset) as u64).is_some() { From 0f46775995188b8e1ecfe0b78ae05be8806957c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Sat, 7 Feb 2026 23:20:26 +0800 Subject: [PATCH 6/6] chore: typo Related: rust-lang/rust#150337 --- src/buffered/bufreader/buffer.rs | 2 +- src/buffered/linewriter/shim.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/buffered/bufreader/buffer.rs b/src/buffered/bufreader/buffer.rs index 641e9ea..e5dfbc6 100644 --- a/src/buffered/bufreader/buffer.rs +++ b/src/buffered/bufreader/buffer.rs @@ -54,7 +54,7 @@ impl Buffer { #[inline] pub fn buffer(&self) -> &[u8] { - // SAFETY: self.pos and self.cap are valid, and self.cap => self.pos, and + // 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 diff --git a/src/buffered/linewriter/shim.rs b/src/buffered/linewriter/shim.rs index eb1fd98..dcb3b8a 100644 --- a/src/buffered/linewriter/shim.rs +++ b/src/buffered/linewriter/shim.rs @@ -44,7 +44,7 @@ impl<'a, W: ?Sized + Write> LineWriterShim<'a, W> { } impl<'a, W: ?Sized + Write> Write for LineWriterShim<'a, W> { - /// Writes some data into this BufReader with line buffering. + /// Writes some data into this BufWriter with line buffering. /// /// This means that, if any newlines are present in the data, the data up to /// the last newline is sent directly to the underlying writer, and data @@ -137,7 +137,7 @@ impl<'a, W: ?Sized + Write> Write for LineWriterShim<'a, W> { self.buffer.flush() } - /// Writes some data into this BufReader with line buffering. + /// Writes some data into this BufWriter with line buffering. /// /// This means that, if any newlines are present in the data, the data up to /// the last newline is sent directly to the underlying writer, and data