RustArena

Step 4 of 6

Fn, FnMut and FnOnce

Every closure has its own anonymous type, invented by the compiler. You can't write that type down, so how does a function accept a closure as a parameter? Through three traits that closures implement automatically, depending on what their body does with the values it captured:

Trait The closure... It can be called
Fn only reads what it captured (or captures nothing) any number of times
FnMut changes what it captured any number of times, but needs mut access
FnOnce moves a captured value out (gives it away) once

They nest: every Fn closure is also FnMut and FnOnce, and every FnMut closure is also FnOnce. Plain functions like fn triple(x: i32) -> i32 implement all three, so you can pass a function's name wherever a closure is expected.

Don't confuse this with move: move decides how the values get into the closure; these traits describe what the body does with them. A move closure that only reads is still Fn.

Taking a closure as a parameter

Use a generic type with a trait bound, like in the generics module:

fn apply<F: Fn(i32) -> i32>(f: F, value: i32) -> i32 {
    f(value)
}

fn repeat<F: FnMut()>(times: usize, mut f: F) {
    for _ in 0..times {
        f();
    }
}

fn finish<F: FnOnce() -> String>(f: F) -> String {
    f()
}

fn triple(x: i32) -> i32 {
    x * 3
}

fn main() {
    let offset = 10;
    println!("{}", apply(|x| x + offset, 5));
    println!("{}", apply(triple, 5));

    let mut beeps = 0;
    repeat(3, || beeps += 1);
    println!("beeps: {beeps}");

    let report = String::from("all systems go");
    println!("{}", finish(move || report));
}
15
15
beeps: 3
all systems go

Which bound should your function ask for? The loosest one that works for your code, because that accepts the most closures: FnOnce if you call it once, FnMut if you call it repeatedly, Fn only when you need shared access (for example, calling it from several threads at once). The standard library does exactly this: Option::unwrap_or_else takes an FnOnce, Vec::retain takes an FnMut.

You can also write the parameter as f: impl Fn(i32) -> i32. It means the same as the generic version, just shorter.

Common mistakes

fn call_twice<F: Fn()>(f: F) {
    f();
    f();
}

fn main() {
    let name = String::from("Ferris");
    let consume = move || {
        let owned = name;
        println!("{owned}");
    };
    call_twice(consume);
}
error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`

let owned = name; moves the String out of the closure, so a second call would have nothing left to move. Borrow instead (println!("{name}")) and the closure becomes Fn.

fn main() {
    let report = String::from("done");
    let send = move || report;
    let first = send();
    let second = send();
    println!("{first} {second}");
}
error[E0382]: use of moved value: `send`

Your task

Write fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32. It calls f on x, then calls f again on that result. For example, apply_twice(|x| x + 3, 10) is 16.

Challenge 2

Write fn call_n_times<F: FnMut()>(n: usize, f: F) that calls f exactly n times. The tests pass closures that change captured variables, like || count += 1. (Calling an FnMut needs mutable access: you'll have to change the parameter a little.)

Challenge 3

Network calls fail sometimes, so services retry them. Write

fn retry<F: FnMut() -> Result<u32, String>>(attempts: u32, op: F) -> Result<u32, String>

It calls op up to attempts times and returns the first Ok it gets, without calling op again after that. If every attempt fails, it returns the last Err. If attempts is 0, it returns Err(String::from("no attempts")) without calling op at all.

Previous: move closures Next: Returning closures