Skip to content

← Closures and Iterators step 17 of 28

Easy Primitives

from_fn and successors: iterators without writing a struct

Build two iterators without declaring a single type.

pub fn drive(script: Vec<u32>) -> Vec<u32>

The answer is two sequences, concatenated.

Part A — a jump walk, with std::iter::from_fn. Start at index 0. At each step, yield script[i], then jump to i + script[i] + 1. Stop when the index falls outside the script. [2, 9, 9, 3, 9, 9, 9, 1, 5] visits index 0 (value 2, jump to 3), index 3 (value 3, jump to 7), index 7 (value 1, jump to 9 — past the end), yielding [2, 3, 1].

Part B — a recurrence, with std::iter::successors. Let seed be the last value part A yielded, or 1 if part A yielded nothing. Emit seed, and then keep emitting x * 2 + 1 as long as the previous value was below 1000. So the value that first reaches 1000 or more is emitted, and it is the last.

script = [2, 9, 9, 3, 9, 9, 9, 1, 5]
  A -> [2, 3, 1]
  B -> [1, 3, 7, 15, 31, 63, 127, 255, 511, 1023]
  answer -> [2, 3, 1, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023]

script = []          ->  [1, 3, 7, 15, 31, 63, 127, 255, 511, 1023]
script = [1000]      ->  [1000, 1000]

An iterator is a thing with a next method

Item 9.16 made that concrete by writing the state machine out as a struct. These three functions make it concrete a second way: they take the closure and build the struct for you.

fn from_fn<T, F: FnMut() -> Option<T>>(f: F) -> FromFn<F>
fn successors<T, F: FnMut(&T) -> Option<T>>(first: Option<T>, succ: F) -> Successors<T, F>
fn repeat_with<T, F: FnMut() -> T>(repeater: F) -> RepeatWith<F>

Look at the bounds. from_fn‘s closure is next: no arguments, returns Option<T>, None ends the iteration. Everything a hand-written Iterator impl does, in one closure — and the state lives in the closure’s captures instead of in struct fields.

from_fn is the general one, and the one to reach for when the source is not a collection: pulling from a channel, reading from a callback API, walking an index by irregular steps. That last one is part A, and it is deliberately not expressible as an adapter chain — the step size depends on the value you just read.

successors is the specialised one: give it a first value and a function from each value to the next, and it threads the state for you. Part B is exactly that shape. Note the closure receives &T — a reference to the previous item — so |&x| ... destructures it.

repeat_with is from_fn for closures that never stop. It is infinite by construction, so it must be paired with take, zip, or a short-circuiting consumer. Related: std::iter::repeat_n(value, n) for a fixed count, and clippy’s manual_repeat_n will point you at it if you write repeat(v).take(n).

The starter’s E0308 is the whole contract in one error

from_fn(|| {
    let v = script[i];
    i += v as usize + 1;
    v                      // <- returns u32
})
error[E0308]: mismatched types
              expected `Option<_>`, found `u32`

A from_fn closure must return Option, because that is how it says “I am finished”. There is no other channel. Wrap the value in Some, and return None when the index runs out.

Better still: since the closure returns Option, ? works inside it. script.get(i) returns Option<&u32>, so let v = *script.get(i)?; handles the out-of-bounds case and the “stop” signal in one line — and, unlike script[i], cannot panic.

then vs then_some

Part B’s predicate wants to produce Some(next) only when the previous value is small enough. Two spellings:

(x < 1000).then(|| x * 2 + 1)     // lazy: closure runs only if true
(x < 1000).then_some(x * 2 + 1)   // eager: value computed either way

Prefer then_some when the value is cheap — clippy’s unnecessary_lazy_evaluations exists to nudge you there, since a closure for a single arithmetic expression is pure ceremony. Prefer then when building the value allocates or can panic.

Termination

Both halves terminate for a reason worth being able to state. Part A’s index strictly increases by at least one each step, so it must eventually leave the slice. Part B’s value strictly increases (x * 2 + 1 > x for any u32), so it must eventually reach 1000. A from_fn or successors whose state does not make progress is an infinite iterator, and collect on an infinite iterator is a hang, not an error.

Grade is compile + tests + clippy -D warnings.

Loading visualization…