We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 17 of 28
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…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.