#112 May 1, 2026

112. Iterator::scan — Fold That Yields Every Step

fold keeps the running state but only hands you the final answer. So you reach for a mut variable plus map, or you give up and collect first. scan is the missing middle: a fold that yields each intermediate step.

The setup is familiar — you want a running total, not just the sum:

1
2
3
4
5
6
7
let nums = [1, 2, 3, 4, 5];

let mut sum = 0;
let totals: Vec<i32> = nums.iter()
    .map(|&x| { sum += x; sum })
    .collect();
assert_eq!(totals, vec![1, 3, 6, 10, 15]);

It works, but the state lives outside the chain. map is supposed to be pure — leaning on a captured mut makes the iterator harder to refactor and impossible to compose.

scan puts the state inside the chain. You hand it an initial value and a closure that mutates the state and returns each yielded item:

1
2
3
4
5
6
7
8
9
let nums = [1, 2, 3, 4, 5];

let totals: Vec<i32> = nums.iter()
    .scan(0, |sum, &x| {
        *sum += x;
        Some(*sum)
    })
    .collect();
assert_eq!(totals, vec![1, 3, 6, 10, 15]);

No captured state, no second pass. The closure returns Option<U>, so returning None ends the iteration early — handy for “stop when the running total crosses a threshold”:

1
2
3
4
5
6
7
8
9
let nums = [3, 4, 5, 6, 7];

let until_ten: Vec<i32> = nums.iter()
    .scan(0, |sum, &x| {
        *sum += x;
        (*sum <= 10).then_some(*sum)
    })
    .collect();
assert_eq!(until_ten, vec![3, 7]);

The state isn’t limited to numbers either — pair it with a tuple to track “previous + current” and you’ve got differences in one pass:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let readings = [10, 13, 12, 18, 20];

let deltas: Vec<i32> = readings.iter()
    .scan(None, |prev, &x| {
        let d = prev.map(|p| x - p);
        *prev = Some(x);
        Some(d)
    })
    .flatten()
    .collect();
assert_eq!(deltas, vec![3, -1, 6, 2]);

Reach for scan whenever you’d otherwise write let mut acc = … outside a map. Same shape, no escapee state.

← Previous 111. Vec::insert_mut — Splice In and Edit Without Reindexing