Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Iterator::take_while take ownership of the iterator?

Tags:

rust

I find it odd that Iterator::take_while takes ownership of the iterator. It seems like a useful feature to be able to take the first x elements which satisfy some function but still leave the rest of the elements available in the original iterator.

I understand that this is incompatible with a lazy implementation of take_while, but still feels useful. Was this just judged not useful enough to include in the standard library, or is there some other problem I'm not seeing?

like image 255
Alex Ozdemir Avatar asked Jul 13 '15 00:07

Alex Ozdemir


2 Answers

All the iterator adapters take the original iterator by value for efficiency's sake. Additionally, taking ownership of the original iterator avoids having to deal with lifetimes when it isn't necessary.

If you wish to retain access to the original iterator, you can use by_ref. This introduces one level of indirection, but the programmer chooses to opt into the extra work when the feature is needed:

fn main() {
    let v = [1, 2, 3, 4, 5, 6, 7, 8];
    let mut i1 = v.iter();
    for z in i1.by_ref().take_while(|&&v| v < 4) {
        //     ^^^^^^^^^
        println!("Take While: {}", z);
    }

    for z in i1 {
        println!("Rest: {}", z);
    }
}

Has the output

Take While: 1
Take While: 2
Take While: 3
Rest: 5
Rest: 6
Rest: 7
Rest: 8

Iterator::by_ref works because there's an implementation of Iterator for any mutable reference to an iterator:

impl<'_, I> Iterator for &'_ mut I
where
    I: Iterator + ?Sized, 

This means that you can also take a mutable reference. The parenthesis are needed for precedence:

for z in (&mut i1).take_while(|&&v| v < 4)

Did you note that 4 was missing? That's because once take_while picks a value and decides to not use it, there's nowhere for it to "put it back". Putting it back would require opting into more storage and slowness than is always needed.

I've used the itertools crate to handle cases like this, specifically take_while_ref:

use itertools::Itertools; // 0.9.0

fn main() {
    let v = [1, 2, 3, 4, 5, 6, 7, 8];
    let mut i1 = v.iter();
    for z in i1.take_while_ref(|&&v| v < 4) {
        //     ^^^^^^^^^^^^^^^
        println!("Take While: {}", z);
    }

    for z in i1 {
        println!("Rest: {}", z);
    }
}
Take While: 1
Take While: 2
Take While: 3
Rest: 4
Rest: 5
Rest: 6
Rest: 7
Rest: 8
like image 51
Shepmaster Avatar answered Nov 18 '22 01:11

Shepmaster


If it's getting too complicated, we may be using the wrong tool.
Note that 4 is present here.

fn main() {
    let v = [1, 2, 3, 4, 5, 6, 7, 8];
    let mut i1 = v.iter().peekable();
    while let Some(z) = i1.next_if(|&n| n < &4) {
        println!("Take While: {}", z);
    }
    for z in i1 {
        println!("Rest: {}", z);
    }
}
Take While: 1
Take While: 2
Take While: 3
Rest: 4
Rest: 5
Rest: 6
Rest: 7
Rest: 8

Yes, the OP asked for take_while and Shepmaster's solution is superb.

like image 3
Kaplan Avatar answered Nov 17 '22 23:11

Kaplan