rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

first() in iterator

Open ananthp opened this issue 5 years ago • 23 comments

std::iter::Iterator has a method to get the last element, last(). But why isn't there a first() to get the first element?

There are times when first() comes handy, especially after filtering and/or sorting the items. Though it's possible to use nth(0) in such cases, first() would be cleaner.

ananthp avatar Dec 07 '19 09:12 ananthp

There is such a method; it is just named next().

pnkfelix avatar Dec 07 '19 09:12 pnkfelix

Ah! next() is all over the documentation, yet I failed to relate to it. Not very intuitive IMHO.

ananthp avatar Dec 07 '19 10:12 ananthp

It might be sufficient to add a note about next in the documentation of last.

mcarton avatar Dec 10 '19 09:12 mcarton

it is not possible for all iterators to access the first element at any time.

aristarh2704 avatar Dec 23 '19 22:12 aristarh2704

@aristarh2704 are you making a distinction between the “first ever” and the “first remaining”? Your statement is true for the former, but not really the latter. The latter is what seems to be being discussed here.

Even for the former, you could have an iterator adapter that saves the first ever seen element and use that on top of any other iterator.

shepmaster avatar Dec 23 '19 23:12 shepmaster

@pnkfelix I somehow disagree. Iterator::next doesn’t consume the iterator. I guess Iterator::first should be defined in terms of:

trait Iterator {
  // …

  fn first(self) -> Option<Self::Item> {
    self.next()
  }
}

hadronized avatar Dec 27 '19 11:12 hadronized

@phaazon why would you want that though?

IMO adding the following to the documentation of last should be enough:

Note that you can also get the first of the remaining elements of the iterator using next.

Someone searching in the doc how to get the first element should find that easily.

mcarton avatar Dec 27 '19 12:12 mcarton

I like @phaazon's suggestion to add first. That would be more intuitive and cleaner than adding a note to next in the doc. However,

  • What's the norm here about multiple methods doing the same thing?
  • Is next common in other languages, more common than first that it's a widely understood? (in which case clarifying it in doc is more appropriate than duplicating functionality)

last consumes the iterator. Would a consuming first make sense? Would it make way for additional optimization as opposed to non-consuming next?

ananthp avatar Jan 02 '20 17:01 ananthp

@phaazon why would you want that though?

IMO adding the following to the documentation of last should be enough:

Note that you can also get the first of the remaining elements of the iterator using next.

Someone searching in the doc how to get the first element should find that easily.

I don’t want to do that, I support doing that. last already exists and consumes. It seems weird to suggest people to have a different interface for first.

Also, most containers in Rust have a first() method. Why not iterators? Especially with such a trivial implementation, non-breaking change addition?

hadronized avatar Jan 03 '20 20:01 hadronized

Keep in mind that this bloats every Rust binary that uses dyn Iterator, so I'd be careful adding stuff that is technically not needed.

CryZe avatar Jan 03 '20 20:01 CryZe

@CryZe That is not the case, because the methods are marked as Self: Sized, so they are not included in the trait object:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4372db0df1f24f8652e33c5473480341

Pauan avatar Jan 03 '20 21:01 Pauan

Also… I’m not even sure it’s a real and useful argument. If the Rust team cared about that, the Iterator trait would be split into several traits. It’s one of the trait in std that has the more methods.

hadronized avatar Jan 05 '20 19:01 hadronized

Honestly, the only version of first that might make sense is one that works like last but calls next_back instead. Probably a hot take.

clarfonthey avatar Feb 02 '20 23:02 clarfonthey

Ideally, first would make sense if it returns the very first element of the iterator, irrespective of the time of call. This also fits nicely with next and last, as next can fetch any value from first to last, last always gets the last value, and first should always get the first value.

ananthp avatar Feb 03 '20 07:02 ananthp

There is no way to know the very first element of an iterators, and for many iterators (like IterMut) returning the very first element after it has been yielded before would be UB.

I.e. iterators can't track it's history in any way in general.

RustyYato avatar Feb 03 '20 14:02 RustyYato

Also .last() doesn't return the very last value if you've called .next_back().

fn main() {
    let mut m = [1, 2, 3, 4, 5].iter();
    m.next_back();
    dbg!(m.last());  // Some(4)
}

kennytm avatar Feb 03 '20 15:02 kennytm

What about something more along the lines of peekable? You'd have a separate struct, something like KnownRange, that remembers the iterator's state when it's created and has methods like first_ever, last_ever, and maybe nth_ever that return values based on that.

Of course, you might want to have both those and peek on the same iterator, and then things get more complicated. Maybe you could do that with corresponding traits for each: impl<I> PeekableIterator for KnownRange<I> where I: PeekableIterator

Saffith avatar Feb 23 '20 18:02 Saffith

@Saffith at that point you may as well just collect the iterator into a Vec

RustyYato avatar Feb 23 '20 18:02 RustyYato

You could clone the iterator to "remember the initial state".

let mut m = [1, 2, 3, 4, 5].iter();
let m_original = m.clone();
m.next_back();
assert_eq!(m_original.last(), Some(&5));

The PeekableIterator would be

struct PeekableIterator<I> {
    original: I,
    current: I,
}

impl<I: Clone> PeekableIterator<I> {
    pub fn new(iter: I) -> Self {
        Self {
            original: iter.clone(),
            current: iter,
        }
    }
    pub fn reset(&mut self) {
        self.current = self.original.clone();
    }
}

impl<I: Iterator> Iterator for PeekableIterator<I> {
    type Item = I::Item;
    fn next(&mut self) -> Option<I::Item> {
        self.current.next()
    }
}
// delegate all other traits to `self.current`.

kennytm avatar Feb 23 '20 18:02 kennytm

@Pauan

@CryZe That is not the case, because the methods are marked as Self: Sized, so they are not included in the trait object:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4372db0df1f24f8652e33c5473480341

Self: Sized methods still take up space in vtables, they are just set to zero. This makes it easy to go from the nth method to the correct position in the vtable: (3+n)*size_of::<usize>()

bjorn3 avatar Jul 14 '20 11:07 bjorn3

An idea for an alternative way to approach this: https://internals.rust-lang.org/t/idea-diagnostic-aliases-attribute/13366?u=scottmcm

scottmcm avatar Nov 10 '20 22:11 scottmcm

As a newcomer to rust, I suggest that if you cannot always get the very first element of the iterator upon its creation, then the suggested implementation of .first() would just be confusing and not what I would expect.

If it does not exist in the data structure, then we should not cover up that fact.

ardijanr avatar Jan 04 '21 19:01 ardijanr

A #[doc(alias = "first")] would fix newcomers confusion, as when you do iter.firs<tab>, the LSP will suggest iter.next().

No new methods that do the exact same thing would be added to the trait, either. A win-win.

RGBCube avatar Jul 06 '24 18:07 RGBCube