Skip to content

← Closures and Iterators step 2 of 28

Medium Primitives

Fn, FnMut, FnOnce: the closure hierarchy

Repair three helpers and use them to double a list, total the doubled values, and build a report. Keep the supplied public signatures and trait bounds:

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)
  • each calls f once per input value, in order, and returns the results.
  • tally calls f once per input value, in order, for its side effects.
  • finish calls f exactly once and returns its string unchanged.

In run_three, call each with a doubling closure. Pass the doubled vector to tally with a closure that adds each value to a captured i64 counter. Then create the report string and pass it to finish in a closure that moves the string out when called. Return (total, doubled, report).

The report is exactly "n=<count> total=<total>", with one space between the fields and no newline. The count is the input length. Preserve input order and duplicates; negative values are allowed. Inputs are chosen so each doubling and each intermediate total fit in i64.

For [1, 2, 3], return (12, [2, 4, 6], "n=3 total=12"). For [], return (0, [], "n=0 total=0").

What the three traits allow

Bound How the call accesses the closure What the helper may do
FnOnce Consumes self Call the owned closure once.
FnMut Borrows &mut self Call repeatedly through mutable access.
Fn Borrows &self Call repeatedly through shared access.

Every closure implements FnOnce. A closure implementing Fn also implements FnMut and FnOnce; one implementing FnMut also implements FnOnce. The traits describe available ways to call it, not how many calls a function actually makes. These helpers' descriptions specify their call counts.

Moving a non-Copy value out of a capture makes a closure FnOnce-only. Mutating an ordinarily captured counter requires FnMut. Reading captures can use Fn. Interior mutability is an important exception to the shorthand "Fn means no state changes": an Fn closure can update a Cell, for example. Fn alone also promises neither thread safety nor independence from call order; concurrency needs appropriate Send and Sync bounds.

In your own APIs, choose a bound that permits the calls you need without unnecessarily restricting callers. For this exercise, keep each's Fn bound even though its loop could also be implemented with FnMut.

Capturing by value is different from moving out

move controls how a closure captures values. It does not by itself make the closure FnOnce-only. This complete example owns a String but only borrows it during each call, so the closure implements Fn:

fn twice<F: Fn() -> usize>(f: F) -> (usize, usize) {
    (f(), f())
}

fn main() {
    let name = String::from("Rust");
    let length = move || name.len();
    assert_eq!(twice(length), (4, 4));
}

By contrast, move || label returns its captured String by value. The first call moves that string out, so that closure cannot be called again. Copying a captured Copy value out does not have this restriction.

Repair the starter

In tally, the owned parameter binding must be mut f: F so the loop can call it through mutable access. Without that change, the compiler reports E0596. Parameter mutability affects the local binding, not the function type.

In finish, the first call consumes f, and the attempted second call produces E0382. The generic FnOnce bound supplies no reusable-call or copy guarantee. Return the result of one call instead.

Finally, replace run_three's todo!() with the three helper calls. Pass the counter closure directly to tally, then read the counter after that call returns. Its mutable borrow has ended by then. More generally, such a borrow can end after the closure's last use; it need not last until the end of the scope that contains the closure binding.

Your solution must compile, produce the required tuple and pass Clippy with warnings treated as errors.