32

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?

2 Answers 2

56

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 
Sign up to request clarification or add additional context in comments.

3 Comments

Is there a syntax sugar to by_ref? This API isn't very friendly.
@MaximilianBurszley I'd say that is the nice form. You can also do (&mut i1).take_while(...)
Eh, better. The documentation wasn't super clear about the ownership part (probably just inexperience speaking here), so this answer and response are appreciated.
7

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 

next_if is called on each element of the iterator

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.