🎓 A1Learner
🔍

Rust More Iterator Adapters

More adapters add memory, stopping, and repeating to an iterator chain. scan remembers a running value.

skip_while drops only the front run

Both lines start from the same numbers. skip_while drops the leading items above 3, then keeps everything — including the 9. filter drops every item above 3, wherever it sits:

let lead: Vec<i32> = vec![5, 1, 2, 9, 3].into_iter().skip_while(|n| *n > 3).collect();
println!("{lead:?}");
let rest: Vec<i32> = vec![5, 1, 2, 9, 3].into_iter().filter(|n| *n <= 3).collect();
println!("{rest:?}");

map_while stops at the first failure

Parsing works until "x" — and the chain ends there. The "15" after it is never reached:

let parsed: Vec<i32> = ["4", "8", "x", "15"]
    .iter()
    .map_while(|s| s.parse::<i32>().ok())
    .collect();
println!("{parsed:?}");

scan keeps a running total

scan is map with a memory. The state starts at 0. Each step adds one item into it. The new total is the item handed on:

let running: Vec<i32> = vec![3, 1, 4, 1]
    .iter()
    .scan(0, |total, &n| {
        *total += n;
        Some(*total)
    })
    .collect();
println!("{running:?}");

flatten removes one level

A Vec of Vecs becomes one flat stream of numbers:

let flat: Vec<i32> = vec![vec![1, 2], vec![3], vec![4, 5]].into_iter().flatten().collect();
println!("{flat:?}");

fuse keeps a finished iterator finished

std::iter::from_fn builds an iterator from a closure. Here the closure is badly behaved. It wakes up with a 9 after returning None. fuse() seals it: after the first None, every next() is None:

let mut calls = 0;
let mut flaky = std::iter::from_fn(move || {
    calls += 1;
    match calls {
        1 => Some(1),
        2 => None,
        _ => Some(9),
    }
})
.fuse();
println!("{:?} {:?} {:?}", flaky.next(), flaky.next(), flaky.next());
println!("{:?}", flaky.next());

Example

fn main() {
    let readings = [2, 4, 6, 99, 8, 10, 12];
    let kept: Vec<i32> = readings.iter().skip_while(|n| **n < 5).copied().collect();
    println!("after warm-up = {kept:?}");
    let parsed: Vec<i32> = ["3", "5", "oops", "7"]
        .iter()
        .map_while(|s| s.parse::<i32>().ok())
        .collect();
    println!("parsed = {parsed:?}");
    let totals: Vec<i32> = parsed
        .iter()
        .scan(0, |run, &n| {
            *run += n;
            if *run > 6 { None } else { Some(*run) }
        })
        .collect();
    println!("running totals = {totals:?}");
    let grid = vec![vec![1, 2], vec![3, 4]];
    let cells: Vec<i32> = grid.into_iter().flatten().collect();
    println!("cells = {cells:?}");
    let lights: Vec<&str> = ["red", "green"].into_iter().cycle().take(5).collect();
    println!("lights = {lights:?}");
}

Key points

  • Use skip_while(test) to drop a leading run only.
  • The first item that fails the test is kept, and everything after it too.
  • That holds even for later items that would pass the test.
  • Use map_while(f) to convert and stop.
  • It works like filter_map, but the first None ends the iterator.
  • The None is not skipped over.
  • Use scan(state, f) for a running value.
  • The closure sees &mut state and each item.
  • It returns Some(next item) — or None to stop the chain.
  • Use flatten() when the items are themselves collections or Options: one level of nesting is removed
  • Use cycle() to repeat a finished iterator from the start, forever.
  • Always pair it with take or another brake.
  • Use fuse() to promise that after the first None the iterator stays finished.
  • Adapters and library code may rely on that promise.
  • flat_map maps and then flattens in one step.
  • flatten only flattens what is already nested.

In this example, each new adapter does one job. skip_while drops the warm-up readings 2 and 4. The 6 fails the test. So it is kept, and everything after it too — even the small 8. map_while parses numbers and stops dead at "oops", so parsed is [3, 5] and the 7 is never tried. scan then walks those two numbers with a running total. 3 is handed on. But adding 5 would take the total to 8, past the limit of 6. So the closure returns None and the chain ends. Running totals is just [3]. Below, flatten pours the two rows of the grid into one stream of cells. cycle repeats the two traffic-light colours. take(5) acts as the brake.

Note

flat_map and flatten are the same idea split in two.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does [2, 4, 6, 1].skip_while(|n| *n < 5) produce?