Why doesn't by_ref prevent .all from consuming the iterator?

Viewed 217

The following prints 9, then true, as expected:

fn main() {
    let mut a = (1..10);
    println!("{}", a.by_ref().count()); // 9
    println!("{}", a.all(|x| x < 10)); // true
}

Playground

The following prints true then 0!

fn main() {
    let mut a = (1..10);
    println!("{}", a.by_ref().all(|x| x < 10)); // true
    println!("{}", a.count()); // 0
}

Playground

Shouldn't calling .by_ref() on the iterator before calling .all prevent .all from consuming the iterator?

3 Answers

Other answers cover what's going on implementation-wise, but I'd like to address the terminology, which I think lies at the root of the confusion:

Shouldn't calling .by_ref() on the iterator before calling .all prevent .all from consuming the iterator?

In Rust consume has a narrow definition related to ownership - a function "consuming" a value takes ownership of the value from the caller. Since the function is free to destroy the value or pass it along, the value is unavailable for further use by the caller that owned it originally.

In case of values implementing Iterator, methods that consume them, such as count() or sum(), typically also exhaust them (use them up). Other methods, such as map() or enumerate(), consume the iterator and return a new iterator, which will exhaust the original one provided that it is exhausted itself. But there are also methods like take() or scan, which consume the iterator without necessarily exhausting it, even when the iterator they return is fully exhausted. With such methods by_ref() comes in handy to keep the original iterator available for further processing:

let mut iter = 1..1000;
let sum_first_50 = iter.by_ref().take(50).sum();
let sum_second_50 = iter.by_ref().take(50).sum();
// ... iter available to produce the remaining 899 numbers ...

Without the by_ref() in the first line, the second line wouldn't compile because the first take() would consume (but not exhaust) iter. With by_ref() iter is neither consumed (because by_ref() protects it) nor exhausted (because we combined it with take()). Combining by_ref() with a method that both consumes and exhausts the iterator, such as count(), is well-defined but useless, because it will just leave you in possession of an exhausted iterator.

If you want to prevent the iterator from getting exhausted (or otherwise mutated), you can instead clone it:

fn main() {
    let a = 1..10;
    println!("{}", a.clone().all(|x| x < 10)); // true
    println!("{}", a.count()); // 9
}

Calling all() or count() on an iterator will invoke next() many times. Even if the iterator is not consumed (moved-from), its internal state has changed due to the repetitive invocations of next().

When all() ends with true, the end of the sequence has been reached, thus count() is still callable but yields 0.

In the first case, count() reaches the end of the sequence, but the documentation of all() says that true is returned for an empty sequence.

Every iterator adapter (such as .take, .filter, and of course, .all) only have access to the elements of the iterator through the .next function.

The only thing that .by_ref does is that it allows you to continue using the iterator after the use of iterator adapters on it. Any consumed element will still be consumed. If you don't want that, you should clone the iterator instead.

This is explained in the documentation for Iterator::by_ref:

let a = [1, 2, 3];

let iter = a.iter();

let sum: i32 = iter.take(5).fold(0, |acc, i| acc + i);

assert_eq!(sum, 6);

// if we try to use iter again, it won't work. The following line
// gives "error: use of moved value: `iter`
// assert_eq!(iter.next(), None);

// let's try that again
let a = [1, 2, 3];

let mut iter = a.iter();

// instead, we add in a .by_ref()
let sum: i32 = iter.by_ref().take(2).fold(0, |acc, i| acc + i);

assert_eq!(sum, 3);

// now this is just fine:
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);
Related