Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

first() in iterator #2833

Open
ananthp opened this issue Dec 7, 2019 · 22 comments
Open

first() in iterator #2833

ananthp opened this issue Dec 7, 2019 · 22 comments

Comments

@ananthp
Copy link

ananthp commented Dec 7, 2019

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.

@pnkfelix
Copy link
Member

pnkfelix commented Dec 7, 2019

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

@ananthp
Copy link
Author

ananthp commented Dec 7, 2019

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

@mcarton
Copy link
Member

mcarton commented Dec 10, 2019

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

@aristarh2704
Copy link

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

@shepmaster
Copy link
Member

@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.

@hadronized
Copy link
Contributor

hadronized commented Dec 27, 2019

@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()
  }
}

@mcarton
Copy link
Member

mcarton commented Dec 27, 2019

@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.

@ananthp
Copy link
Author

ananthp commented Jan 2, 2020

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?

@hadronized
Copy link
Contributor

@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?

@CryZe
Copy link

CryZe commented Jan 3, 2020

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.

@Pauan
Copy link

Pauan commented Jan 3, 2020

@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

@hadronized
Copy link
Contributor

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.

@clarfonthey
Copy link
Contributor

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.

@ananthp
Copy link
Author

ananthp commented Feb 3, 2020

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.

@RustyYato
Copy link

RustyYato commented Feb 3, 2020

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.

@kennytm
Copy link
Member

kennytm commented Feb 3, 2020

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)
}

@Saffith
Copy link

Saffith commented Feb 23, 2020

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

@RustyYato
Copy link

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

@kennytm
Copy link
Member

kennytm commented Feb 23, 2020

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`.

@bjorn3
Copy link
Member

bjorn3 commented Jul 14, 2020

@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>()

@scottmcm
Copy link
Member

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

@ardijanr
Copy link

ardijanr commented Jan 4, 2021

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.

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

No branches or pull requests