Table of Contents
Understanding the distinction between Fn
, FnMut
, and FnOnce
traits is crucial for mastering Rust's closure system, ownership, and performance characteristics.
Closure Capturing
Closures in Rust capture variables from their environment in one of three ways, depending on how the variables are used:
- Immutable Borrow (
&T
): If the closure only reads a variable. - Mutable Borrow (
&mut T
): If the closure modifies a variable. - Ownership (
T
): If the closure takes ownership (e.g., viamove
or by consuming the variable).
The compiler automatically infers the least restrictive capture mode needed. The move
keyword forces ownership capture, but the closure’s trait (Fn
, FnMut
, or FnOnce
) depends on how the captured variables are used.
Closure Traits
Rust closures implement one or more of these traits:
Trait | Captures Variables Via | Call Semantics | Call Count |
---|---|---|---|
Fn |
Immutable borrow (&T ) |
&self |
Multiple |
FnMut |
Mutable borrow (&mut T ) |
&mut self |
Multiple |
FnOnce |
Ownership (T ) |
self (consumes closure) |
Once |
Key Differences
Fn
:- Can be called repeatedly.
- Captures variables immutably.
- Example:
let x = 42; let closure = || println!("{}", x); // Fn (captures `x` by &T)
FnMut
:- Can mutate captured variables.
- Requires
mut
keyword if stored. - Example:
let mut x = 42; let mut closure = || { x += 1; }; // FnMut (captures `x` by &mut T)
FnOnce
:- Takes ownership of captured variables.
- Can only be called once.
- Example:
let x = String::from("hello"); let closure = || { drop(x); }; // FnOnce (moves `x` into closure)
Trait Hierarchy
Fn
: Also implementsFnMut
andFnOnce
.FnMut
: Also implementsFnOnce
.- A closure that implements
Fn
can be used whereFnMut
orFnOnce
is required. - A closure that implements
FnMut
can be used asFnOnce
.
move
Keyword
Forces the closure to take ownership of captured variables, even if they’re only read:
let s = String::from("hello");
let closure = move || println!("{}", s); // `s` is moved into the closure
- Trait Impact:
- If the closure doesn’t mutate or consume
s
, it still implementsFn
(sinces
is owned but not modified). - If the closure consumes
s
(e.g.,drop(s)
), it becomesFnOnce
.
- If the closure doesn’t mutate or consume
Examples
Immutable Capture (
Fn
):let x = 5; let print_x = || println!("{}", x); // Fn print_x(); // OK print_x(); // Still valid
Mutable Capture (
FnMut
):let mut x = 5; let mut add_one = || x += 1; // FnMut add_one(); // x = 6 add_one(); // x = 7
Ownership Capture (
FnOnce
):let x = String::from("hello"); let consume_x = || { drop(x); }; // FnOnce consume_x(); // OK // consume_x(); // ERROR: closure called after being moved
Performance & Use Cases
Trait | Overhead | Use Case |
---|---|---|
Fn |
Zero-cost | Read-only callbacks, iterators |
FnMut |
Zero-cost | Stateful transformations |
FnOnce |
May allocate | One-time operations (e.g., spawning threads) |
Key Takeaways
✅ Fn
: Read-only, reusable.
✅ FnMut
: Mutable, reusable.
✅ FnOnce
: Owned, single-use.
🚀 move
forces ownership but doesn’t change the trait—usage determines the trait.
Try This: What happens if a closure captures a mutable reference but doesn’t mutate it?
Answer: It still implements FnMut
(since it could mutate), but you can pass it to a function expecting FnMut
.