Skip to content

Refactor and simplify Reader and Writer traits#213

Merged
cpubot merged 3 commits intomasterfrom
io-rewrite
Mar 17, 2026
Merged

Refactor and simplify Reader and Writer traits#213
cpubot merged 3 commits intomasterfrom
io-rewrite

Conversation

@cpubot
Copy link
Copy Markdown
Contributor

@cpubot cpubot commented Mar 3, 2026

This PR refactors Reader and Writer traits in an attempt to simplify implementations and reduce trait surface area.

Removal of Trusted Associated Type

Reader and Writer both have an associated type Trusted, which must implement either Reader or Writer respectively. We aren't getting value out of Trusted being designated as an associated type, as SchemaRead / SchemaWrite implementations simply take an impl Reader or impl Writer argument -- there isn't any way to further constrain an implementation based on a Reader's or Writer's Trusted associated type, and I can't think of a use-case where we would want that functionality (and would require adjusting read / write to take a generic Reader or Writer type parameter, which is different, more complicated refactor).

As such, the signatures of as_trusted_for now return ReadResult<impl Reader<'a>> or WriteResult<impl Writer>, for Reader and Writer, respectively.

A nice result of this is that we can now provide a default implementation for as_trusted_for. In particular, by default we can simply return Ok(self). This

  1. Removes a ton of boilerplate in the trusted Reader/Writer implementations (they're already eliding bounds checks, no need to build and return another instance of itself)
  2. Means that implementations that don't have a practical way of implementing as_trusted_for can simply not implement it
  3. Means that defining Reader and Writer only requires implementing copy_into_slice and write, respectively.

A fun consequence of 3 is that we can easily implement something like core::iter::from_fn, but for Reader or Writer. Unsure if this has a concrete use-case at the moment, but could see value as a convenience in io based as_trusted_for implementations.

Additionally, we no longer have to deal with generic associated types (GATs), which get quite cumbersome to deal with and ugly in the Reader case for non-borrowing types. E.g.,

impl<'a, T> Reader<'a> for Cursor<T>
where
T: AsRef<[u8]>,
{
type Trusted<'b>
= TrustedSliceReader<'a, 'b>
where
Self: 'b;

vs
impl<'a, T> Reader<'a> for Cursor<T>
where
T: AsRef<[u8]>,
{
#[inline(always)]
unsafe fn as_trusted_for(&mut self, n_bytes: usize) -> ReadResult<impl Reader<'a>> {
let window = self.advance_slice_checked(n_bytes)?;
Ok(unsafe { SliceScopedUnchecked::new(window) })
}

Much nicer!

Refactor of TrustedSliceReader, TrustedSliceReaderZeroCopy, Trusted....

We had a proliferation of specialized Trusted*Reader*/Trusted*Writer*, etc. These are all subsumed by three, more general types.

  1. SliceUnchecked
  2. SliceScopedUnchecked
  3. SliceMutUnchecked

This is a result of an observation that all Trusted* permutations were effectively abstractions over an in-memory slice, with slight semantic differences:

  1. slice reader with zero-copy capability
  2. slice reader with without zero-copy capability
  3. slice reader/writer with zero-copy capability

All as_trusted_for implementations in the library now use one of the three, which simplifies everything.

Additionally, due to Reader and Writer no longer having an associated Trusted type, these implementations can totally elide all trusted machinery.

impl<'a> Reader<'a> for TrustedSliceReaderZeroCopy<'a> {
type Trusted<'b>
= Self
where
Self: 'b;
#[inline]
fn fill_buf(&mut self, n_bytes: usize) -> ReadResult<&[u8]> {
Ok(trusted_slice::fill_buf(self.cursor, n_bytes))
}
#[inline]
fn fill_exact(&mut self, n_bytes: usize) -> ReadResult<&[u8]> {
Ok(trusted_slice::fill_exact(self.cursor, n_bytes))
}
#[inline]
fn borrow_exact(&mut self, len: usize) -> ReadResult<&'a [u8]> {
let (src, rest) = unsafe { self.cursor.split_at_unchecked(len) };
self.cursor = rest;
Ok(src)
}
#[inline]
unsafe fn consume_unchecked(&mut self, amt: usize) {
trusted_slice::consume_unchecked(&mut self.cursor, amt);
}
#[inline]
fn consume(&mut self, amt: usize) -> ReadResult<()> {
trusted_slice::consume(&mut self.cursor, amt);
Ok(())
}
#[inline]
unsafe fn as_trusted_for(&mut self, n_bytes: usize) -> ReadResult<Self::Trusted<'_>> {
Ok(TrustedSliceReaderZeroCopy::new(self.borrow_exact(n_bytes)?))
}
}

vs
impl<'a> Reader<'a> for SliceUnchecked<'a, u8> {
#[inline]
fn copy_into_slice(&mut self, buf: &mut [MaybeUninit<u8>]) -> ReadResult<()> {
let chunk = unsafe { advance_slice_unchecked(&mut self.buf, buf.len()) };
unsafe { copy_nonoverlapping(chunk.as_ptr(), buf.as_mut_ptr().cast(), buf.len()) };
Ok(())
}
#[inline(always)]
fn take_array<const N: usize>(&mut self) -> ReadResult<[u8; N]> {
let chunk = unsafe { advance_slice_unchecked(&mut self.buf, N) };
Ok(unsafe { *(chunk.as_ptr().cast::<[u8; N]>()) })
}
#[inline]
fn borrow_exact(&mut self, len: usize) -> ReadResult<&'a [u8]> {
let chunk = unsafe { advance_slice_unchecked(&mut self.buf, len) };
Ok(chunk)
}
}

@kskalski kskalski self-requested a review March 4, 2026 00:27
Copy link
Copy Markdown
Contributor

@kskalski kskalski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are awesome changes!
A couple of minor comments / questions.

kskalski
kskalski previously approved these changes Mar 5, 2026
Copy link
Copy Markdown
Contributor

@kskalski kskalski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

}

#[test]
fn test_reader_consume_input_too_large(bytes in any::<Vec<u8>>()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should keep the test I guess

kskalski
kskalski previously approved these changes Mar 16, 2026
@cpubot cpubot force-pushed the io-rewrite branch 4 times, most recently from cbb9819 to d06d2f9 Compare March 17, 2026 00:21
@cpubot cpubot merged commit 023d7f5 into master Mar 17, 2026
4 checks passed
@cpubot cpubot deleted the io-rewrite branch March 17, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants