RustArena

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

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