Introduction
Every closure in Rust implements one or more of three traits — Fn, FnMut, and FnOnce — determined by how the closure captures and uses variables from its environment. Understanding this hierarchy is the foundation for writing generic APIs that accept closures.
Key Concepts
- FnOnce: A closure that can be called at least once. It may consume (move) captured variables, so calling it a second time is not guaranteed to be safe.
- FnMut: A closure that can be called multiple times and may mutate its captured state. It borrows captured variables mutably.
- Fn: A closure that can be called any number of times without side effects on its captures. It borrows captured variables immutably (or captures nothing).
- Trait hierarchy:
Fnis a subtrait ofFnMut, which is a subtrait ofFnOnce. AnyFnclosure satisfiesFnMut, and anyFnMutclosure satisfiesFnOnce.
Real World Context
Every time you pass a closure to .map(), .filter(), thread::spawn(), or any generic callback API, the compiler decides which trait that closure implements. Understanding these traits lets you write functions that accept the widest or narrowest set of closures appropriate for your use case.
Deep Dive
The compiler determines a closure's trait based on what the closure body does with captured variables. Let's walk through each trait with clear examples.
FnOnce — consumes captured values
A closure that moves a captured value out of itself can only be called once, because the value is gone after the first call:
rustlet name = String::from("Alice"); let consume = || { drop(name); // Moves `name` out, consuming it }; consume(); // OK — first call // consume(); // Error! `name` was consumed on the first call
After consume() runs, name no longer exists inside the closure. The compiler marks this closure as FnOnce only.
FnMut — mutates captured values
A closure that changes a captured variable needs mutable access, so it implements FnMut:
rustlet mut count = 0; let mut increment = || { count += 1; // Mutably borrows `count` }; increment(); // count = 1 increment(); // count = 2
Notice that both the binding count and the closure binding increment must be declared mut. This closure also satisfies FnOnce (it can certainly be called once), but it does not satisfy Fn because it mutates state.
Fn — immutable access only
A closure that only reads captured variables (or captures nothing) implements Fn:
rustlet greeting = String::from("Hello"); let greet = || println!("{greeting}"); // Immutably borrows `greeting` greet(); // "Hello" greet(); // Can call as many times as needed
This closure satisfies all three traits: Fn, FnMut, and FnOnce.
Writing generic functions with closure bounds
When you write a function that takes a closure, choose the least restrictive bound that works:
rust// Accepts any closure (even consuming ones) fn call_once<F: FnOnce() -> String>(f: F) -> String { f() } // Accepts closures that can be called multiple times (may mutate) fn call_twice<F: FnMut() -> i32>(mut f: F) -> i32 { f() + f() } // Accepts only pure closures (no mutation) fn call_many<F: Fn() -> i32>(f: F) -> i32 { f() + f() + f() }
Using FnOnce as a bound is the most flexible because it accepts all closures. Using Fn is the most restrictive.
Common Pitfalls
- Requiring
FnwhenFnOncesuffices — If you only call the closure once, useFnOnceas the bound. UsingFnunnecessarily rejects valid closures that consume their captures. - Forgetting
mutonFnMutclosures — Both the closure variable and the parameter in a generic function must be markedmutwhen calling anFnMutclosure. - Confusing the hierarchy direction —
Fnis the most restrictive trait (fewest closures qualify), not the least. Think of it as:Fn⊂FnMut⊂FnOnce.
Best Practices
- Default to
FnOncefor single-call callbacks — This accepts the widest range of closures. Tighten toFnMutorFnonly when you need to call the closure multiple times. - Use
impl Fn(...)in argument position — For most application code,impl Fn(i32) -> i32is cleaner than a named generic parameter. - Annotate return types with
impl Fn— When returning closures, use-> impl Fn(...)and addmoveif the closure captures local variables.
Summary
- Closures implement
FnOnce,FnMut, orFnbased on how they use captures. - The trait hierarchy is
Fn⊂FnMut⊂FnOnce. - Choose the least restrictive bound (
FnOnce) unless you need repeated calls. Fnclosures can be shared freely;FnMutclosures requiremutaccess.- Rust 1.94 makes closure capturing more precise around pattern bindings.
Code Examples
// Demonstrating the trait hierarchy with a practical example
fn apply_twice<F: Fn(i32) -> i32>(f: F, value: i32) -> i32 {
f(f(value))
}
fn apply_and_collect<F: FnMut(i32) -> i32>(mut f: F, items: Vec<i32>) -> Vec<i32> {
items.into_iter().map(|x| f(x)).collect()
}
fn consume_and_report<F: FnOnce() -> String>(f: F) {
println!("Result: {}", f());
}
fn main() {
// Fn closure — no mutation, no consumption
let multiplier = 3;
let triple = |x| x * multiplier;
let result = apply_twice(triple, 5); // 5 -> 15 -> 45
println!("apply_twice: {result}");
// FnMut closure — tracks call count
let mut call_count = 0;
let counting_double = |x| {
call_count += 1;
x * 2
};
let doubled = apply_and_collect(counting_double, vec![1, 2, 3]);
// doubled = [2, 4, 6], call_count = 3
// FnOnce closure — consumes a String
let message = String::from("task complete");
consume_and_report(|| message); // `message` is moved
}