When the compiler argues about your callback
You're writing a function that accepts a closure. You write fn process<F>(f: F). The compiler rejects you. You add a trait bound. You try F: Fn(). It still rejects you. You switch to F: FnOnce(). Now it compiles, but you can't call the closure inside a loop. You're staring at three traits that look identical except for the suffix, and the compiler is treating them like completely different types.
The difference isn't just syntax. It's a contract about ownership and mutation that the compiler enforces to keep your memory safe. Fn, FnMut, and FnOnce describe exactly what a closure is allowed to do with the variables it captures from its environment. Get the trait wrong, and the compiler blocks you. Get it right, and the compiler guarantees your closure behaves exactly as promised.
The contract of capture
A closure is a function that captures variables from its surrounding scope. The traits define the permissions that closure has over those captured variables.
Think of a closure as a worker you send out with a set of tools. The traits describe what the worker is allowed to do with those tools.
Fn means the worker can use the tools as many times as needed, but the tools must remain unchanged. The worker borrows the tools immutably. When the worker returns, the tools are exactly as they were.
FnMut means the worker can use the tools multiple times and modify them. The tools might get worn down, marked up, or rearranged. The worker borrows the tools mutably. The tools still exist after the work is done, but their state may have changed.
FnOnce means the worker takes the tools, does one job, and consumes them. The tools are gone after that. Maybe the tool was a single-use battery, or a sealed envelope that gets opened and destroyed. The worker takes ownership of the tools.
The compiler infers which trait your closure implements by looking at what you do inside the closure body. You rarely write impl Fn manually. You write the logic, and the compiler picks the most permissive trait that satisfies your needs.
Minimal example
/// Demonstrates the three closure traits based on capture behavior.
fn main() {
// Fn: Captures immutably. Can be called many times.
let count = 5;
let read_only = || println!("Count is {}", count);
read_only();
read_only(); // Works because count is not moved or mutated.
// FnMut: Captures mutably. Can be called many times.
let mut score = 0;
let mut increment = || score += 1;
increment();
increment(); // Works because score is mutated, not moved.
// FnOnce: Takes ownership. Can be called exactly once.
let secret = String::from("top_secret");
let consume = || println!("Secret: {}", secret);
consume();
// consume(); // Error: value moved into closure.
}
The compiler looks at read_only and sees it only reads count. It assigns Fn. It looks at increment and sees it mutates score. It assigns FnMut. It looks at consume and sees it moves secret into the println! call. It assigns FnOnce.
The hierarchy and inference
The traits form a strict hierarchy. Fn is the most restrictive. FnOnce is the least restrictive.
If a closure implements Fn, it automatically implements FnMut and FnOnce. If it implements FnMut, it automatically implements FnOnce. This means Fn is a subtype of FnMut, and FnMut is a subtype of FnOnce.
This hierarchy matters when you pass closures to functions. A function that expects Fn is strict. It promises not to mutate the closure and to call it multiple times. A closure that implements Fn satisfies this contract. A closure that implements FnMut does not satisfy Fn because it might mutate captured state, which violates the immutability promise.
You can pass a Fn closure to a function expecting FnMut. The function promises to allow mutation, and the closure says it won't mutate. That's safe. You cannot pass a FnMut closure to a function expecting Fn. The function promises not to mutate, but the closure might need to. That's a violation.
The compiler always infers the weakest trait that works. If your closure only reads, it gets Fn. If it mutates, it gets FnMut. If it moves, it gets FnOnce. You can force a stronger trait by changing the closure body, but you cannot force a weaker trait. If you move a value, the closure is FnOnce. You can't convince the compiler it's Fn by adding a trait bound. The capture semantics are fixed by the code.
Trust the compiler's inference. It picks the weakest trait that satisfies your logic. If it says FnOnce, you moved something. Check your captures.
Realistic example: retry logic
You're building a retry mechanism for a fallible operation. You want to accept a closure that returns Option<T> and call it up to three times.
/// Retries a fallible operation up to three times.
/// Requires FnMut because the closure might mutate state between attempts.
fn retry<F, T>(mut op: F) -> Option<T>
where
F: FnMut() -> Option<T>,
{
for _ in 0..3 {
if let Some(result) = op() {
return Some(result);
}
}
None
}
fn main() {
let mut attempts = 0;
let result = retry(|| {
attempts += 1; // Mutates captured state.
if attempts == 2 {
Some(42)
} else {
None
}
});
println!("Result: {:?}", result);
}
The bound is FnMut. The closure captures attempts and mutates it. If you changed the bound to Fn, the compiler would reject this with E0277 (trait bound not satisfied). The closure implements FnMut, not Fn.
Notice mut op: F. When a function takes a FnMut closure, the closure itself must be mutable to call it. The compiler enforces this. If you forget mut, you get E0596 (cannot borrow as mutable). The closure needs to be mutable because calling it might mutate its captured environment.
Convention aside: iterator adapters like map, filter, and for_each usually take FnMut. This allows stateful closures. You can pass a counter or an accumulator to map and it works. If they took Fn, you couldn't use stateful closures. If they took FnOnce, you couldn't iterate at all.
Realistic example: thread spawning
You want to spawn a thread that consumes a resource. The thread takes ownership of the data.
use std::thread;
/// Spawns a thread that consumes the closure.
/// Requires FnOnce because the thread takes ownership of captured values.
fn spawn_task<F, T>(task: F) -> thread::JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
thread::spawn(task)
}
fn main() {
let data = String::from("owned by thread");
let handle = spawn_task(|| {
println!("Thread has: {}", data);
data.len()
});
let len = handle.join().unwrap();
println!("Length: {}", len);
}
The bound is FnOnce. The closure captures data and moves it into the thread. The thread owns the data. The closure can only be called once because the data is moved.
If you tried to call the closure twice, the compiler would reject you with E0382 (use of moved value). The data moved into the closure on the first call. It's gone.
Don't force FnOnce unless you need to move. If you can share data with Rc or Arc, or borrow it with references, you might get Fn or FnMut. But for thread spawning, FnOnce is the natural fit. Threads take ownership.
Pitfalls and compiler errors
Closures often surprise you with their trait inference. Here are the common traps.
Method calls change the trait. Calling a method that takes &self keeps the closure Fn. Calling a method that takes &mut self makes the closure FnMut. Calling a method that takes self makes the closure FnOnce.
let mut v = vec![1, 2, 3];
// Fn: len takes &self.
let len_fn = || v.len();
// FnMut: push takes &mut self.
let push_fnmut = || v.push(4);
// FnOnce: into_iter takes self.
let consume_fnonce = || v.into_iter().count();
If you write a function expecting Fn and the user passes a closure that calls push, the compiler rejects it. The closure is FnMut. Relax the bound to FnMut.
The move keyword forces ownership. Adding move to a closure forces it to capture all variables by value. If any captured variable doesn't implement Copy, the closure becomes FnOnce.
let s = String::from("hello");
// FnOnce: move forces ownership of s.
let closure = move || println!("{}", s);
Even if you only read s, the move keyword moves it into the closure. The closure is FnOnce. You can't call it twice. Remove move if you only need to read, or clone the value if you need to call twice.
Hidden moves in captures. Sometimes you think you're borrowing, but you're moving. This happens with types that don't implement Copy.
let data = vec![1, 2, 3];
let closure = || {
let _x = data; // Moves data.
};
// closure(); // Error: value moved.
The closure captures data by move because _x takes ownership. The closure is FnOnce. If you want Fn, borrow data explicitly or use a reference.
The compiler knows exactly what your closure does. If it says FnOnce, it means you moved something. Don't fight it. Check your captures.
Decision matrix
Use Fn when the closure reads captured variables without changing them and you need to call it multiple times. This is the standard for read-only logic, pure transformations, and functions that guarantee no side effects on captured state.
Use FnMut when the closure needs to mutate captured state, like a counter, an accumulator, or a buffer, and you call it multiple times. This is common for stateful iterators, retry loops, and callbacks that track progress.
Use FnOnce when the closure consumes captured values, moving ownership into the closure body. Use this for spawning threads, one-time initialization, consuming a resource exactly once, or when the closure captures a non-Copy type and you don't need to call it again.
Reach for Fn as the default bound for generic functions. It accepts the widest range of closures because Fn implies FnMut and FnOnce. Only relax to FnMut or FnOnce when your function body actually requires mutation or consumes the closure. A function that calls the closure once can accept FnOnce, but if it calls it multiple times, it needs FnMut or Fn.
Treat the trait bound as a promise. If you bound Fn, you promise not to mutate. If you bound FnOnce, you promise to consume. The compiler enforces these promises to keep your code safe.