Step 3 of 7
Laziness: nothing happens until you ask
Adapters don't do any work when you call them. map and filter just build a new iterator value that remembers what to do. The work happens only when a consumer starts calling next(). This is called laziness, and the compiler warns you when you forget:
let v = vec![1, 2, 3];
v.iter().map(|x| println!("{x}")); // prints nothing!
warning: unused `Map` that must be used
--> src/main.rs:3:5
|
3 | v.iter().map(|x| println!("{x}"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: iterators are lazy and do nothing unless consumed
For side effects like printing, use a for loop (or the consumer .for_each(|x| ...)). map is for transforming values.
One item at a time
Laziness also means items flow through the chain one by one. Each item goes through every step before the next item starts:
fn main() {
let total: i32 = [1, 2, 3, 4]
.iter()
.filter(|x| {
println!("filter {x}");
*x % 2 == 0
})
.map(|x| {
println!("map {x}");
x * 10
})
.sum();
println!("total {total}");
}
filter 1
filter 2
map 2
filter 3
filter 4
map 4
total 60
No temporary vector is built between filter and map. That's why chains are as fast as loops.
Infinite iterators and stopping early
Because nothing runs until asked, an iterator can even be infinite: (1..) counts up forever. That's fine as long as something stops asking:
fn main() {
let first_squares: Vec<u64> = (1u64..).map(|n| n * n).take(5).collect();
println!("{:?}", first_squares);
let readings = [3, 5, 8, -1, 4, 6];
let before_error: Vec<i32> = readings.iter().copied().take_while(|r| *r >= 0).collect();
let after_two: Vec<i32> = readings.iter().copied().skip(2).collect();
let countdown: Vec<u32> = (0..=10).rev().step_by(5).collect();
println!("{:?}", before_error);
println!("{:?}", after_two);
println!("{:?}", countdown);
}
[1, 4, 9, 16, 25]
[3, 5, 8]
[8, -1, 4, 6]
[10, 5, 0]
| Adapter | What comes out |
|---|---|
take(n) |
the first n items, then it stops |
skip(n) |
everything after the first n items |
take_while(f) |
items while f says true; stops at the first false |
skip_while(f) |
drops items while f says true, then yields all the rest |
step_by(k) |
the first item, then every k-th item after it |
rev() |
the items backwards (works on ranges, vectors, .chars(): things that know their end) |
Common mistakes
- An infinite chain with nothing to stop it.
(1..).map(|n| n * 2).collect::<Vec<u64>>()never finishes (here it would hit the time limit). - Order matters.
.take(3).filter(...)takes the first three items and then filters them;.filter(...).take(3)gives the first three items that pass the filter. - Expecting
mapto run on its own. Without a consumer, its closure never runs.
Your task
Write fn first_multiples(k: u32, n: usize) -> Vec<u32> that returns the first n multiples of k (k, 2k, 3k, ...), skipping any multiple that ends in 0 (divisible by 10). For example, first_multiples(5, 4) is [5, 15, 25, 35]. Start from the infinite range (1..), no loops. (k is never a multiple of 10.)
Challenge 2
A sensor sends readings; a negative reading means the sensor failed, and everything after it is garbage. Write fn readings_until_error(readings: &[i32]) -> Vec<i32> that returns the readings before the first negative one. Use take_while, no loops.
Challenge 3
Read a whole number n from input. Print the even numbers from n down to 0 on one line, separated by spaces, then liftoff! on the next line. For 10:
10 8 6 4 2 0
liftoff!
Build the line with a range, rev(), an adapter that keeps the evens, map(|x| x.to_string()) and collect::<Vec<String>>() (or let parts: Vec<String> = ...), then parts.join(" "). No loops.
Previous: From loops to iterator chains Next: Combining iterators: enumerate, zip, chain, flat_map, windows