Skip to content

Conversation

onkoe
Copy link

@onkoe onkoe commented Sep 27, 2025

Goal

Improve the Offset + Stream docs.

Background

I implemented Stream for a type yesterday, but it took quite a lot of time to understand what was going on. The existing docs weren't very clear, so it kinda required me to copy what &[u8] and &str were doing.

After implementing it successfully, I thought I may as well share what I learned! (and spare myself the time re-learning it in the future)

These changes should clarify what an implementation should do... without being too over the top. If I still sounded a little off, or anything is incorrect, please let me know and I'll fix it up! :D

Changes

  • Improve docs on winnow::stream::Stream...
  • ...and on winnow::stream::Offset.
  • Fix a couple broken intradoc links.

Testing

I know it'll run in CI, but I tested my changes like so:

  • typos
  • committed
  • cargo doc

previously, the docs in `Offset` and `Stream` made implementing it for a
type yourself pretty difficult.

I had some trouble, so thought I'd spare myself the time in the future
by improving it for everyone :D
Comment on lines +138 to +139
///
/// Types can implement this trait to become custom input types.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This feels redundant

Suggested change
///
/// Types can implement this trait to become custom input types.

Comment on lines +157 to +160
/// An iterator containing a tuple of:
///
/// - [`usize`]: the offset from the current location.
/// - [`Self::Token`]: the token at the `usize` location.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess https://doc.rust-lang.org/std/str/struct.CharIndices.html would be more closely aligned with intent

Comment on lines +161 to +164
///
/// Note that, since [`Iterator`] is a trait, this type can be anything
/// that implements that trait. Therefore, an efficient implementation will
/// dynamically create this tuple instead of precomputing it.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This feels oddly specific and more about teaching Rust than explaining this functionality

Comment on lines +171 to +173
/// Note that this type must implement [`Offset`], which says how many
/// bytes have been computed between a checkpoint and some initial/starting
/// checkpoint.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is redundant with the trait bounds and their own documentation as well as inaccurate as we talk about Offsets, not Bytes.

@coveralls
Copy link

Pull Request Test Coverage Report for Build 18062781148

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 7 unchanged lines in 4 files lost coverage.
  • Overall coverage decreased (-0.1%) to 46.802%

Files with Coverage Reduction New Missed Lines %
src/ascii/mod.rs 1 65.96%
src/combinator/multi.rs 2 53.94%
src/stream/mod.rs 2 45.37%
src/stream/range.rs 2 18.18%
Totals Coverage Status
Change from base Build 17156771731: -0.1%
Covered Lines: 1610
Relevant Lines: 3440

💛 - Coveralls

Comment on lines +167 to +169
/// A saved location within the [`Stream`].
///
/// This type should allow users to reset the stream to a previous state.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we have reset link to Self::reset? If so, then we should probably also link to Self::checkpoint()? This also quickly becomes redundant with those.

Maybe

Suggested change
/// A saved location within the [`Stream`].
///
/// This type should allow users to reset the stream to a previous state.
/// A saved location within the [`Stream`].
///
/// See also [`Self::checkpoint`] and [`Self::reset`]

Comment on lines +176 to +179
/// Creates a [`Self::IterOffsets`] from the existing stream state.
///
/// For efficiency, this should probably be computed on the fly, at least
/// when it's possible.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is too implementation focused. Most people will be using an existing stream type. Their goal isn't to create a IterOffsets but to iterate with offsets. Might be good to take inspiration from https://doc.rust-lang.org/std/string/struct.String.html#method.char_indices for wording

fn iter_offsets(&self) -> Self::IterOffsets;

/// Returns the offset to the end of the input
/// Returns the number of bytes until the stream completes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

offset, not bytes

fn eof_offset(&self) -> usize;

/// Split off the next token from the input
/// Takes the next token from the stream.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't express that we are also advancing the stream

Comment on lines +189 to +190
/// This method should consume the token, mutating the original input to
/// no longer have it.
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should word this in terms that work for both user and implementer

Comment on lines +199 to +200
/// Finds the offset of the next value matching the given `predicate`
/// function.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Finds the offset of the next value matching the given `predicate`
/// function.
/// Finds the offset of the next token that matches `predicate`

Comment on lines +201 to +202
///
/// The offset is relative from the current location.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if there is a better way to teach this concept because everything is relative to the stream's current location

Comment on lines +209 to +210
/// The offset is relative to the current position. This means that an
/// input of "0 tokens" will return an offset of `0`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

P: Fn(Self::Token) -> bool;
/// Get the offset for the number of `tokens` into the stream

/// Find the offset after consuming a number of `tokens` from the stream.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"consuming" makes this sound like this advanced the stream's position.

Comment on lines +219 to +222
/// **Note:** For inputs with variable width tokens, like `&str`'s `char`,
/// `offset` might not correspond with the number of tokens. To get a valid
/// offset, use:
///
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did nothing change here? If so, please revert

Comment on lines +213 to +215
/// Takes a slice of `offset` tokens from the input.
///
/// This method should consume `offset` tokens.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel like saying "split off" covers both sentences and ensures its all in the summary

/// # Panic
///
/// This will panic if
/// This method panics if any of the following invariants are violated:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please revert. This doesn't add anything but does make it more likely for the user to miss whats being said

fn next_slice(&mut self, offset: usize) -> Self::Slice;
/// Split off a slice of tokens from the input

/// Takes a slice of `offset` tokens from the input.
Copy link
Collaborator

Choose a reason for hiding this comment

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

FYI this is the point that I'm taking a break. Please consider my feedback above on the documentation below and we can re-evaluate once its all been updated

@onkoe
Copy link
Author

onkoe commented Sep 29, 2025

Thanks for your feedback! Note that my primary reason for making these changes is to increase accessibility for implementers. :D

The stable docs on this trait make it overly confusing to implement for the first time. So, I focused on making it easier to understand. If you'd like me to remove those elements and assume that all readers are very experienced winnow users, please let me know!

(will take a better look at these suggestions later this week; your notes on sounding more like the stl and being more careful w/ "consuming" vs. "peeking" are veeeery important)

@epage
Copy link
Collaborator

epage commented Sep 29, 2025

The stable docs on this trait make it overly confusing to implement for the first time. So, I focused on making it easier to understand. If you'd like me to remove those elements and assume that all readers are very experienced winnow users, please let me know!

I am fine with making the docs more approachable. My concerns are:

  • over-optimizing for implementers when the vast majority of people won't implement it
  • going beyond covering winnow topics to covering general Rust topics. If there is a caveat to why there is special handling needed, you'll need to provide context

@onkoe
Copy link
Author

onkoe commented Sep 29, 2025

Got it! I'll approach this again later with that mindset. Thanks for the feedback 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants