Rust Patterns That Matter #15: Fn, FnMut, FnOnce
Post 15 of 22 in Rust Patterns That Matter. Companion series: Building a Chat Server in Rust.
Previous: #14: Enum Dispatch | Next: #16: Storing Closures
You pass a closure to a function and get a trait mismatch. You didn't ask for any
trait. You just wrote a || block. What's happening involves how closures
capture their environment, and it's one of the most useful things to understand about
Rust.
The motivation
fn call_twice(f: impl Fn()) {
f();
f();
}
fn main() {
let mut count = 0;
call_twice(|| {
count += 1; // ERROR
});
}
error[E0525]: expected a closure that implements the `Fn` trait,
but this closure only implements `FnMut`
The closure mutates count. The function requires Fn, which
doesn't allow mutation. The compiler knows what your closure does and what the
function expects, and they don't match.
The three traits
Every closure in Rust implements at least one of three traits, determined by how it uses captured variables:
FnOnce- can be called once. The closure might consume (move out of) a captured value. After that, the value is gone and the closure can't be called again.FnMut- can be called multiple times, but might mutate captured values. Requires exclusive (&mut) access to the closure each time it's called.Fn- can be called multiple times with only shared (&) access. Doesn't mutate or consume anything from its environment.
The hierarchy
Fn (most restrictive — only reads) ↑ FnMut (middle — reads and mutates) ↑ FnOnce (least restrictive — reads, mutates, consumes)
Every Fn closure is also FnMut (mutating nothing is a
valid form of mutation). Every FnMut closure is also FnOnce
(calling something once is a valid form of "at least once"). The hierarchy goes
upward: if a function accepts FnOnce, you can pass any closure.
If it requires Fn, only non-mutating closures qualify.
What determines the trait
The compiler picks the most permissive trait your closure qualifies for:
let name = String::from("Alice");
// Only reads `name` → implements Fn (and FnMut, and FnOnce)
let greet = || println!("Hello, {name}");
let mut count = 0;
// Mutates `count` → implements FnMut (and FnOnce), but NOT Fn
let mut increment = || count += 1;
let data = vec![1, 2, 3];
// Moves `data` out → implements FnOnce only
let consume = || drop(data);
The compiler examines every captured variable and how it's used. Reading:
Fn. Mutating: FnMut. Moving out: FnOnce.
The closure gets the least restrictive trait that covers all its captures.
The callee constrains it
The function you pass the closure to declares which trait it requires. That's the constraint:
// "I'll call this multiple times, read-only" → Fn
fn call_twice(f: impl Fn()) { f(); f(); }
// "I'll call this multiple times, it may mutate" → FnMut
fn call_twice_mut(mut f: impl FnMut()) { f(); f(); }
// "I'll call this exactly once" → FnOnce
fn call_once(f: impl FnOnce()) { f(); }
call_twice requires Fn because it calls the closure twice
without exclusive access. If your closure mutates, it doesn't satisfy Fn,
and the compiler rejects it. Change the requirement to FnMut and it
works:
let mut count = 0;
call_twice_mut(|| count += 1); // OK — FnMut accepted
println!("{count}"); // 2
The move keyword
A common confusion: move changes ownership, not the trait.
A move closure takes ownership of captured variables instead of
borrowing them. But if it only reads those variables, it's still Fn:
let name = String::from("Alice");
let greet = move || println!("Hello, {name}");
// `name` is moved into the closure, but only read
// → still implements Fn
greet(); // fine
greet(); // fine — Fn, so can be called multiple times
move is about how data enters the closure (ownership vs borrowing).
The trait is about how the closure uses that data (read, mutate, consume).
These are independent.
When to use which
If you're writing a function that takes a closure:
Fn— for callbacks called repeatedly, possibly concurrently (event handlers, validators)FnMut— for operations called sequentially that might accumulate state (Iterator::for_each,fold)FnOnce— for one-shot operations (thread::spawn,Option::map,Result::unwrap_or_else)
Default to the least restrictive trait your function needs. If you
only call the closure once, use FnOnce - it accepts all closures.
Only require Fn if you genuinely need to call it multiple times
without exclusive access.
See it in practice: Building a Chat Server #4: Commands and Plugins uses this pattern for the message filter plugin system.
Telex