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
F: Fn(i32) -> i32reads "F is something callable that takes ani32and returns ani32". The bound looks like a function signature.repeatcallsfseveral times andfchanges things, so it asks forFnMut, and its parameter ismut f.finishcallsfonly once, soFnOnceis enough.move || reporthandsreportback to the caller, giving it away, so that closure is onlyFnOnce: it couldn't be passed toapplyorrepeat.apply(triple, 5)passes a plain function where a closure is expected.
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
- Passing an
FnOnceclosure whereFnis needed:
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.
- Calling an
FnOnceclosure twice:
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`
- Forgetting
muton anFnMutparameter:fn repeat<F: FnMut()>(times: usize, f: F)fails withcannot borrow `f` as mutable. Writemut f: F.
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.