When the compiler rejects your callback
You're writing a callback for a UI button, a retry loop, or a sort comparator. You grab a variable from the surrounding scope, write a few lines of logic, and pass the closure to a function. The compiler rejects you with E0277. The error message says the closure does not implement the Fn trait. You look at the code. The closure seems perfectly fine. It just reads a value, or maybe updates a counter. Why is Rust refusing to accept it?
The error isn't about syntax. It's about a contract. The function you're calling demands a closure that behaves in a specific way. Your closure behaves differently. The compiler caught the mismatch before you could ship a bug.
The baggage your closure carries
In Rust, a closure is more than a block of code. It's a block of code plus the environment it captures. When you write |x| x + multiplier, the closure doesn't just contain the addition logic. It also holds a reference to multiplier. That reference is baggage. The closure carries it around, and the baggage determines what the closure can do.
Rust categorizes closures into three tiers based on how they treat their baggage. The tiers are Fn, FnMut, and FnOnce. The error E0277 appears when your closure falls into a lower tier than the function expects.
Think of the tiers like permissions on a shared document. Fn is a read-only link. Anyone with the link can view the document, but nobody can edit it. You can share the link a thousand times, and the document stays the same. FnMut is an editable link. People can change the document, but you need exclusive access to make those changes. FnOnce is a one-time transfer. The document gets handed over, and the original link disappears.
When a function signature requires Fn, it's asking for a read-only link. It promises to call the closure multiple times without altering the captured state. If your closure tries to edit the document, it's no longer read-only. It's an editable link. The function refuses it.
The three tiers: Fn, FnMut, FnOnce
The three traits correspond to how the closure's call method works under the hood. This is the technical mechanism that enforces the rules.
Fn closures implement call(&self). The closure takes an immutable reference to itself. This means the closure can read its captured variables, but it cannot change them. Because the reference is immutable, you can call the closure as many times as you want, even from multiple threads if the captured data is thread-safe. Fn is the most restrictive tier for the closure author, but the most flexible for the caller.
FnMut closures implement call(&mut self). The closure takes a mutable reference. This allows the closure to mutate its captured variables. You can call the closure multiple times, but you need mutable access to the closure itself. If you store the closure in a variable, that variable must be mut. You cannot call an FnMut closure from multiple threads simultaneously without synchronization.
FnOnce closures implement call(self). The closure takes ownership of itself. This allows the closure to move captured variables out of its environment. After the call, the closure is consumed. You can only call it once. This tier covers closures that capture non-copy types by value and use them up.
The hierarchy flows downward. Every Fn closure is also FnMut. Every FnMut closure is also FnOnce. A function that accepts FnOnce will accept any closure. A function that accepts Fn will only accept closures that never mutate their environment.
How the compiler picks the tier
You don't annotate closures with Fn or FnMut. The compiler infers the tier by analyzing the closure body. It looks at how the closure uses its captured variables.
If the closure only reads captured variables, the compiler assigns Fn. If the closure mutates a captured variable, the compiler assigns FnMut. If the closure moves a captured variable out of the environment, the compiler assigns FnOnce. The compiler always picks the most permissive tier that satisfies the logic. It never assigns Fn if the closure mutates state, even if you think the mutation is harmless.
This inference happens at the definition site. Once the compiler decides the tier, the closure's type is fixed. You cannot coerce an FnMut closure into Fn later. The mismatch triggers E0277 when you try to pass the closure to a function that demands a stricter tier.
Minimal example: The mutation trap
Here is the classic scenario. You have a function that calls a callback twice. You write a closure that increments a counter. The compiler rejects you.
/// Calls the provided closure twice.
/// Requires Fn because the closure might be called multiple times
/// and the function doesn't mutate the closure's state.
fn call_twice<F>(f: F)
where
F: Fn(),
{
f();
f();
}
fn main() {
let mut counter = 0;
// This closure captures `counter` by mutable reference.
// It mutates the captured variable, so it implements FnMut, not Fn.
let closure = || {
counter += 1;
println!("Counter is now {}", counter);
};
// E0277: the trait `Fn` is not implemented for the closure.
// The closure implements FnMut, but call_twice demands Fn.
call_twice(closure);
}
The error message points to the call site. It tells you the closure implements FnMut but the bound requires Fn. The fix depends on your intent. If the closure must mutate state, the function signature is too restrictive. If the function must guarantee no mutation, the closure logic is wrong.
Realistic example: Stateful processing
In real code, this error often appears in generic functions that process collections. You write a helper that maps over items, and you pass a closure that maintains some state.
/// Processes a slice of items using a transformation function.
/// The bound requires Fn because the function iterates multiple times
/// and doesn't need to mutate the closure.
fn process_items<F>(items: &[i32], transform: F) -> Vec<i32>
where
F: Fn(i32) -> i32,
{
let mut result = Vec::with_capacity(items.len());
for item in items {
result.push(transform(*item));
}
result
}
fn main() {
let items = vec![10, 20, 30];
let mut offset = 100;
// This closure captures `offset` mutably.
// It increments the offset for each item, creating a running total.
let transform = |x| {
offset += 1;
x + offset
};
// E0277: transform implements FnMut, not Fn.
// process_items demands a read-only closure.
process_items(&items, transform);
}
Here, the closure needs to mutate offset. The function process_items demands Fn. The mismatch is clear. You have two paths to fix this. You can relax the function bound to FnMut, or you can rewrite the closure to avoid mutation.
If you change the bound to FnMut, the function accepts stateful closures. This is usually the right choice for generic helpers. The community convention is to prefer FnMut in generic function bounds unless you have a specific reason to demand Fn. FnMut accepts both Fn and FnMut closures, so it imposes fewer restrictions on the caller. Standard library methods like Iterator::map and Iterator::filter use FnMut for this reason.
/// Updated to accept FnMut, allowing stateful transformations.
fn process_items<F>(items: &[i32], transform: F) -> Vec<i32>
where
F: FnMut(i32) -> i32,
{
let mut result = Vec::with_capacity(items.len());
for item in items {
result.push(transform(*item));
}
result
}
If you cannot change the function signature, you must make the closure Fn-compatible. This means removing mutation. You might clone the captured variable, or use a different algorithm that doesn't require state.
fn main() {
let items = vec![10, 20, 30];
let offset = 100;
// Immutable capture. No mutation. Implements Fn.
let transform = |x| x + offset;
process_items(&items, transform);
}
Fixing the signature vs fixing the closure
When you hit E0277, ask yourself which side is wrong. If the function is a generic helper you wrote, check the bound. Did you write Fn when FnMut would work? Tightening the bound to Fn forces callers to write more restrictive closures. It breaks valid use cases. Loosen the bound to FnMut unless you need to call the closure in parallel or store it in a struct that requires Sync.
If the function is from a library, you can't change the signature. You must adapt the closure. If the closure mutates a variable, consider using RefCell for interior mutability. RefCell allows mutation through an immutable reference, which can satisfy Fn bounds. This is a trade-off. You gain compile-time flexibility but pay a runtime borrow check. Use this pattern sparingly.
Another option is the move keyword. move changes how the closure captures variables. Instead of borrowing, it moves ownership into the closure. If you capture a Copy type like i32 by move, the closure owns a copy. Mutating that copy doesn't affect the outer scope. The closure can still be FnMut if it mutates the copy. move doesn't automatically make a closure Fn. It only helps if you need to bypass lifetime issues or transfer ownership.
Pitfalls and hidden captures
Closures capture variables implicitly. Sometimes the capture is wider than you expect. If you capture a mutable reference to a struct, the closure might be FnMut even if you only read a field. The compiler sees the reference is mutable, so it assumes the closure could mutate the struct. The closure gets the FnMut tier.
Check your captures carefully. If you only need to read a field, capture the field directly or use an immutable reference. Avoid capturing &mut when & suffices. This is a common source of E0277 in complex code.
Another pitfall is capturing self in methods. If you write a closure inside a method and capture self, the closure captures &self or &mut self depending on usage. If the closure mutates self, it captures &mut self. The closure becomes FnMut. If you pass this closure to a function requiring Fn, you get E0277. The fix is often to capture specific fields instead of self, or to use self.clone() if the struct is cloneable.
Generic bounds can also interact with other traits. If a function requires Fn + Send, the closure must be thread-safe. Capturing non-Send types like Rc or RefCell breaks Send. You might get E0277 for Send instead of Fn. Read the full error message. It lists all missing traits.
Decision matrix
Use Fn when the callback must be callable multiple times without changing its captured state, such as in a pure transformation function or a read-only filter. Use Fn when you need to store the closure in a struct and call it from multiple threads, though you must also ensure the closure implements Send and Sync.
Use FnMut when the callback needs to mutate captured variables, like incrementing a counter, appending to a vector, or updating a cache. Use FnMut as the default bound for generic functions that accept callbacks, since it accepts both Fn and FnMut closures and imposes fewer restrictions on the caller.
Use FnOnce when the closure consumes its captured environment, such as moving a value out of a captured variable or calling a function that takes ownership. Use FnOnce for one-shot operations like thread spawners, Option::map, or Result::and_then.
Reach for move closures when you need to transfer ownership of captured variables into the closure, bypassing lifetime checks by moving data rather than borrowing it. Reach for move when the closure outlives the scope where it was created, such as passing a closure to a background thread.