rust-lang / libs-team

The home of the library team
Apache License 2.0
127 stars 19 forks source link

`Peek` trait for peekable iterators #176

Open bluebear94 opened 1 year ago

bluebear94 commented 1 year ago

Proposal

Problem statement

Some iterator types can support peeking à la Peekable. Sometimes, peeking is required for such an iterator, but methods specific to the original iterator’s type must be called as well. In these cases, creating a Peekable does not allow doing the latter.

Motivation, use-cases

The following code for ps9 is meant to parse a ‘decoration’ object at the start of a word, returning the parsed object as well as the remainder of the string.

impl Decoration {
    /// Parses a [`Decoration`] at the beginning of the string,
    /// returning it and the remainder of the input.
    ///
    /// This operation always succeeds as it can always parse an
    /// empty decoration at the beginning.
    ///
    /// # Examples
    ///
    /// ```
    /// let word = "*#feris";
    /// let (decoration, remainder) = Decoration::parse(word);
    /// assert_eq!(decoration.premarker, PreMarker::Carþ);
    /// assert!(decoration.has_nef);
    /// assert!(!decoration.has_sen);
    /// assert_eq!(remainder, "feris");
    /// ```
    pub fn parse(s: &str) -> (Decoration, &str) {
        let mut s = s.chars().peekable();
        let has_nef = s.next_if_eq(&'*').is_some();
        let premarker_char = s.next_if(|c| "#+×@".contains(*c));
        let premarker = match premarker_char {
            Some('#') => PreMarker::Carþ,
            Some('+') => match s.next_if_eq(&'*') {
                Some(_) => PreMarker::Njor,
                None => PreMarker::Tor,
            },
            Some('×') => PreMarker::Njor,
            Some('@') => PreMarker::Es,
            _ => PreMarker::None,
        };
        let has_sen = s.next_if_eq(&'&').is_some();
        (
            Decoration {
                premarker,
                has_nef,
                has_sen,
            },
            // ERROR: no method named `as_str` found for struct `Peekable` in the current scope
            // method not found in `Peekable<Chars<'_>>`
            s.as_str(),
        )
    }
}

Solution sketches

We could add a Peek: Iterator trait that would provide the same methods as Peekable. This trait would be implemented by Peekable as well as any other iterator that can support peeking operations, such as std::str::Chars, std::slice::Iter, and std::ops::Range.

This trait could be added to all types that are Iterator + Clone, but this would incur unnecessary overhead for types such as std::vec::IntoIter which can peek at O(1) time but needs O(n) time to clone.

Links and related work

What happens now?

This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.

pitaj commented 1 year ago

One method I think would be really nice to have in the standard library is something like peeking_take_while - a version of take_while that doesn't consume the first element that doesn't match. Could live on this trait.

I think another motivation is that Peekable requires stack memory overhead and may not be as performant as impl PeekingIterator for slice::Iter

cuviper commented 1 year ago

Sorry if I missed this in prior discussions, but itertools does have PeekingNext and peeking_take_while.

joshtriplett commented 6 days ago

We discussed this in today's libs-api meeting. We'd like to accept this, with the name PeekableIterator. It should have the peek() method, as well as provided methods for next_if and next_if_eq.

In the future, if we add language support for implementing supertraits via subtraits, we might want to split out the concept of Peek from PeekableIterator (and the concept of HasItem from Iterator).

Note that in general we don't use able adjective names for traits (eg. we have Copy and Write, not Copyable and Writable). However, in this case Iterator is already a trait (rather than Iterate), and we're using an adjective because it's a modifier on Iterator.