Rust closures—anonymous, self-contained function expressions—are central to functional patterns and API design in Rust. Their behavior is governed by three core traits: FnOnce, FnMut, and Fn. These are not arbitrary distinctions but precise compile-time contracts tied to ownership, mutability, and call semantics.
Core Trait Differences
The following table summarizes how each trait governs closure usage:
| Trait | Call Count | Mutable Access to Captured Values | Ownership Transfer of Captures | Typical Use Case |
|---|---|---|---|---|
FnOnce |
Exactly once | Yes (if moved) | Yes — consumes captured values | One-shot callbacks, e.g., thread startup logic |
FnMut |
Multiple times | Yes — via mutable borrow | No — borrows mutably | Stateful iterators, accumulators, event handlers with internal state |
Fn |
Multiple times | No — only immutable borrow | No — borrows immutably | Pure transformations, mapping, filtering, shared callbacks |
Key clarifications:
- Ownership transfer applies only to captured variables—not function arguments.
- The
movekeyword forces copying or moving captured values into the closure’s environment, regardless of trait. It does not dictate which traits implemented—it affects how captures are stored, not what interface they expose. - A closure that mutates a captured variable cannot implement
Fn, becauseFnrequires immutable access. It will always satisfy at leastFnMut(and thereforeFnOnce). - Closures automatically implement all applicable traits. For example, an immutable-capturing closure implements all three; a mutating one implements
FnOnceandFnMut, but neverFn.
Capture Semantics Explained
A variable is captured when it is used inside a closure but declared outside its scope:
let title = String::from("Quantum Algorithms");
let printer = || println!("Book: {}", title); // `title` is captured
printer();
Capture mode inference:
- No
move, no mutation → Captures by immutable reference → ImplementsFn,FnMut,FnOnce. - No
move, but mutation → Captures by mutable reference → ImplementsFnMutandFnOnce(but notFn). - With
move, no mutation → Takes ownership of captures → ImplementsFnOnce; may also implementFnorFnMutif the owned types support borrowing (e.g.,Stringallows immutable/mutable borrows after move). - With
move, and mutation → Owns and mutates → ImplementsFnOnce;FnMutpossible if type permits mutable dereferencing (e.g.,Box<T>orVec<T>).
Determining Implemented Traits Programmatically
You can verify trait conformance using generic functions constrained by each trait:
fn accepts_once<F: FnOnce()>(f: F) { f(); }
fn accepts_mut<F: FnMut()>(mut f: F) { f(); f(); }
fn accepts_fn<F: Fn()>(f: F) { f(); f(); }
fn main() {
let data = String::from("hello");
// Immutable capture → satisfies all three
let c1 = || println!("{}", data);
accepts_once(c1.clone()); // OK
accepts_mut(c1.clone()); // OK
accepts_fn(c1); // OK
let mut counter = 0;
// Mutable capture → satisfies FnOnce & FnMut only
let c2 = || { counter += 1; };
accepts_once(c2.clone()); // OK
accepts_mut(c2); // OK
// accepts_fn(c2); // ❌ Compile error
}
Practical Usage Patterns
These traits enable flexible, type-safe abstractions:
- Callback registration: APIs accept
Box<dyn Fn()>for reusable listeners. - Iterator adaptors:
map()expectsF: Fn(&T) -> U, whilefor_each()usesF: FnMut(&T). - Thread spawning:
std::thread::spawnrequiresFnOnce + Sendbecause the closure runs exactly once in another thread.
Real-World Example: Stateful Book Processor
#[derive(Debug, Clone)]
struct Publication {
name: String,
year: u16,
}
impl Publication {
fn new(name: &str, year: u16) -> Self {
Publication { name: name.into(), year }
}
}
fn main() {
let mut catalog = vec![
Publication::new("Rust in Action", 2021),
Publication::new("The Rust Programming Language", 2018),
];
// FnMut: modifies external vector
let mut adder = || catalog.push(Publication::new("Zero To Production", 2022));
adder();
// Fn: safe to share across threads or reuse
let formatter = || catalog.iter().for_each(|p| println!("{} ({})", p.name, p.year));
formatter();
formatter(); // ✅ Works twice
// FnOnce: consumes a value
let consumer = move || {
let first = catalog.remove(0);
println!("Consumed: {}", first.name);
};
consumer();
// consumer(); // ❌ Not allowed
}