Suggestion: a `.take(n)`-like Iterator method that does not take ownership

Ever wanted to write this kind of code ?

fn main ()
{
    let mut iterator = (0 .. 10).into_iter();
    for x in iterator.take(3) {
        println!("{} is in the podium!", x);
    };
    for x in iterator {
        println!("{} is not, but maybe next time?", x);
    };
}

Obviously it cannot work since .take(n) takes (duh) ownership of iterator.

This can however be fixed by (mutably) borrowing iterator for the time of that first iteration:

fn main ()
{
    let mut iterator = (0 .. 10).into_iter();
    for x in (0 .. 3).filter_map(|_| iterator.next()) { // `FnMut` closure captures `&mut iterator`
        println!("{} is in the podium!", x);
    }; // &mut iterator is dropped here
    for x in iterator {
        println!("{} is not, but maybe next time?", x);
    };
}

The mutable borrow (stored in the .filter_map() closure environment) is released at the end of the iteration and thus we didn’t lose ownership of \iterator`; that’s why we are perfectly able to give it to the second iteration.

However, such tricks

  1. may not be known/understood by beginners,
  2. are definitely not very readable / idiomatic

Hence my suggestion of a .fetch(n) method:

fn main ()
{
    let mut iterator = (0 .. 10).into_iter();
    for x in iterator.fetch(3) { // captures `&mut iterator`
        println!("{} is in the podium!", x);
    };  // &mut iterator is dropped here
    for x in iterator {
        println!("{} is not, but maybe next time?", x);
    };
}

The only remaining question is whether or not to ensure that those n elements are always consumed by the Fetch iterator, in case of, for instance, a break.
Here is a suggestion of a .fetch(n) implementation where those n first values are not acessible by the main iterator no matter how the iteration went: https://play.rust-lang.org/?version=beta&mode=debug&edition=2018&gist=90c73b2cbcd6d4c0da46c30a250a5728

If the break-to-avoid-consumption behavior is preferred, simply comment the Drop trait implementation (I guess there could be both a .fetch(n) and .fetch_upto(n) methods to give the caller the option to chose the appropriate behavior)

1 Like

You can use Iterator::by_ref here, which is more general since it works with more than just take.

8 Likes

Another option is to just take a mutable reference:

fn main ()
{
    let mut iterator = (0 .. 10).into_iter();
    for x in (&mut iterator).take(3) { // captures `&mut iterator`
        println!("{} is in the podium!", x);
    };  // &mut iterator is dropped here
    for x in iterator {
        println!("{} is not, but maybe next time?", x);
    };
}
5 Likes

Ok big mistake from here, I was convinced that &mut Iterator would be an Iterator over &mut Item which is absurd when you stop and think about it :grimacing:

This post deserves the useless code award :sweat_smile::sweat_smile:

EDIT: one thing remains useful from my example, though, and it’s that in case of an incomplete iteration (e.g. with a break), the remaining lazily-fetched values are actually fetched when dropped

3 Likes

I, for one, did not know that &mut I implemented Iterator for I : Iterator, so this discussion taught it to me, I guess?

Iterator is really rust’s finest trait :slight_smile:

2 Likes