Now, we will see some examples that show why iterators are so useful. Iterators are lazy and have to be activated by invoking a consumer to start using the values. Let's start with a range of the numbers from 0 to 999 included. To make this into a vector, we apply the function collect() consumer:
// see code in Chapter 5/code/adapters_consumers.rs let rng = 0..1000; let rngvec: Vec<i32> = rng.collect(); // alternative notation: // let rngvec = rng.collect::<Vec<i32>>(); println!("{:?}", rngvec);
This prints out the range (we shortened the output with...):
[0, 1, 2, 3, 4, ... , 999]
The function collect() loops through the entire iterator, collecting all of the elements into a container, here of type Vec<i32>. That container does not have to be an iterator. Notice that we indicate the item type of the vector with Vec<i32>, but we could have also written it as Vec<_>. The notation collect::<Vec<i32>>() is new, it indicates that the function collect() is a parametrized method that can work with generic types. See the next section for more information.
The function find() consumer gets the first value of the iterator that makes its condition (here >= 42) true and returns it as a value of the type Option, for example:
let forty_two = rng.find(|n| *n >= 42); println!("{:?}", forty_two); //
This prints out the following output:
Some(42)
The value of the function find() is of type Option because the condition could be false for all items and then it would return a None value. The condition is wrapped in a closure |n| *n >= 42, which is applied on every item of the iterator through a reference n; that's why we have to dereference *n to get the value.
Suppose we only want the even numbers in our range, producing a new range by testing a closure condition on each item. This can be done with the filter() function, which is an adapter because it produces a new iterator from the old one. Its result can be collected just like any iterator:
let rng_even = rng.filter(|n| is_even(*n)) .collect::<Vec<i32>>(); // (1) println!("{:?}", rng_even);
Here, is_even is the following function:
fn is_even(n: i32) -> bool { n % 2 == 0 }
This prints out the following output:
[0, 2, 4, ..., 996, 998]
This shows that odd integers are filtered out.
Notice how we can chain our consumers and adapters, just apply collect() on the result of the filter() function with .collect(), as in the previous line 1.
What if we want to cube (n * n * n) every item in the resulting iterator? Producing a new range by applying a closure to each item in it can be done with the map() function:
let rng_even_pow3 = rng.filter(|n| is_even(*n)) .map(|n| n * n * n) .collect::<Vec<i32>>(); println!("{:?}", rng_even_pow3);
This prints out the following output:
[0, 8, 64, ..., 988047936, 994011992]
If you only want the first five results, insert a take(5) adapter before the collect. The resulting vector then contains the following:
[0, 8, 64, 216, 512]
So, call a consumer if you see the following message while compiling:
warning: unused result which must be used: iterator adaptors are lazy and do nothing unless consumed
To see all consumers and adapters, consult the docs of module std::iter.
Another very powerful consumer is the fold() function.
The following example calculates the sum of the first hundred integers. It starts with a base value 0, which is also the initial value of the accumulator sum, and then iterates and adds every item n to sum:
let sum = (0..101).fold(0, |sum, n| sum + n);
println!("{}", sum); //prints out 5050
Now calculate the product of all cubes of the integers in the range from 1 to 6.
The result should be 1728000; watch out for the base value! As a second exercise, subtract all items from the following array [1, 9, 2, 3, 14, 12] starting from 0 (that is, 0 -1 -9 -2 and so on).
This should result in 41 (for example code, see Chapter 5/exercises/fold.rs).