We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Closures and Iterators step 2 of 28
Fn, FnMut, FnOnce: the closure hierarchy
Three tiny generic helpers, one per rung of the closure ladder, and one function that uses all three.
pub fn each<F: Fn(i64) -> i64>(nums: &[i64], f: F) -> Vec<i64>
pub fn tally<F: FnMut(i64)>(nums: &[i64], f: F)
pub fn finish<F: FnOnce() -> String>(f: F) -> String
pub fn run_three(nums: Vec<i64>) -> (i64, Vec<i64>, String)
run_three must:
-
call
eachwith a closure that doubles, producing the second element of the tuple; -
call
tallywith a closure that adds each doubled number into a counter captured from the enclosing scope, producing the first element; -
call
finishwith a closure that moves a capturedStringout, producing the third element — exactly"n=<count> total=<total>", where<count>is how many numbers there were.
For [1, 2, 3] that is (12, [2, 4, 6], "n=3 total=12"). For [] it is
(0, [], "n=0 total=0").
The ladder
There is no single “closure trait”. There are three, and they are nested:
Fn ⊂ FnMut ⊂ FnOnce
Read them as how many times can I call this, and what does calling it do to the captures:
| Trait |
call receives |
Callable | A closure gets it when |
|---|---|---|---|
FnOnce |
self |
once | always — every closure implements it |
FnMut |
&mut self |
many, needs mut |
it does not move its captures out |
Fn |
&self |
many, shared | it neither moves nor mutates them |
So the rule for what a given closure implements is mechanical:
-
Does it move a capture out of itself? Then it is
FnOnceonly. -
Otherwise, does it mutate a capture? Then it is
FnMut(andFnOnce). -
Otherwise it is
Fn(andFnMut, andFnOnce).
And the rule for what you should write in a bound is the mirror image:
take the weakest bound that lets your body work. F: FnOnce() accepts
strictly more closures than F: Fn(), because every Fn closure is also an
FnOnce closure. A bound is a demand you make on your caller; demanding
Fn when you only call once turns away perfectly good callers.
Reading someone else’s signature runs backwards, and this is the practical
payoff: F: FnOnce tells you your closure will be called at most once,
so it is safe to hand it something you are giving away. F: FnMut tells you
it may be called many times and may accumulate state. F: Fn tells you it
may be called many times, possibly from several places at once, and had
better not depend on being called in any particular order.
The misconception this problem exists to kill
“
movemakes a closureFnOnce.”
No. This closure is move, and it is Fn:
let x = 5;
let c = move || x + 1;
println!("{} {} {}", c(), c(), c()); // fine
move decides how captures get in (by value rather than by reference).
The trait is decided by what the body does to them. move || x + 1 takes
a copy of x and then only reads it, so it is Fn. What makes a closure
FnOnce-only is moving a capture out — like the third helper here, whose
closure returns a captured String by value. That String is gone after one
call, so a second call cannot exist. Item 9.3 is about move on its own.
The starter does not compile — read both errors
It ships with two deliberate bugs, one per lesson.
In tally: E0596, cannot borrow \f` as mutable`. Calling an
FnMut closure needs &mut self, so the binding holding it must be
mutable. fn tally<F: FnMut(i64)>(nums: &[i64], f: F) does not give you
that; mut f: F does. Note that mut on a parameter is not part of the
function’s type — callers neither see it nor care. It is a property of the
local binding only.
You will meet this error a second time at the call site, on your own
counter-mutating closure: a closure that mutates a capture is FnMut, and
binding it to a non-mut let gives the same E0596. Two separate errors,
same rule, and it is worth seeing both.
In finish: E0382, use of moved value: \f``. The starter calls f()
twice. FnOnce::call_once takes self by value, so the first call consumes
f. There is no way to call an FnOnce twice; the type system is telling
you the truth. Delete the second call.
Sequencing the borrows
Your counter closure holds a &mut to the counter for as long as the closure
is alive. You cannot read the counter while that loan is live. If the
compiler objects when you go to build the report, the fix is not RefCell —
it is to let the closure die first, which usually means writing the tally
call as its own statement and reading the counter afterwards. Item 9.27 is a
whole problem about exactly this reordering.
Grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.