12.2 Closure Traits: FnOnce
, FnMut
, and Fn
How a closure interacts with its captured environment determines which of the three closure traits it implements: FnOnce
, FnMut
, and Fn
. These traits dictate whether the closure consumes (takes ownership of), mutates (takes an exclusive borrow of), or only reads (takes a shared borrow of) its environment.
Implementation Hierarchy (from most restrictive to least restrictive on capture, or from least to most callable):
The traits form an implementation hierarchy based on the closure’s capabilities:
FnOnce
: This is the most permissive trait regarding what it does with captured data, but the least permissive regarding how many times it can be called. A closure implementingFnOnce
can be called at least once, potentially consuming (moving) its captured environment in the process. All closures implicitly implementFnOnce
.FnMut
: Closures implementingFnMut
can be called multiple times and can mutate their captured environment through exclusive borrows. They do not consume the environment. AllFn
closures also implementFnMut
.Fn
: This is the most restrictive trait regarding what it does with captured data, but the most permissive regarding how many times it can be called. Closures implementingFn
can be called multiple times and only require shared access (or no access) to their environment. They take shared borrows of captured data.
This means Fn
implies FnMut
, and FnMut
implies FnOnce
. A closure implementing Fn
can be used anywhere an FnMut
or FnOnce
is expected; an FnMut
can be used where an FnOnce
is expected.
The compiler automatically determines the most specific trait(s) (Fn
, FnMut
, or just FnOnce
) that a closure implements based on how its body interacts with captured variables.
Usage as Trait Bounds:
Functions accepting closures use these traits as bounds in their generic signatures (e.g., <F: FnMut(i32) -> i32>
). When used this way, the hierarchy relates to the permissiveness of the bound – what kinds of closures the function accepts:
F: FnOnce(...)
: This is the most permissive (least restrictive) bound. It accepts any closure matching the signature (Fn
,FnMut
, orFnOnce
), as it only requires the closure to be callable at least once.F: FnMut(...)
: This bound is more restrictive. It accepts closures implementingFnMut
orFn
(sinceFn
impliesFnMut
), requiring the closure to be callable multiple times, potentially taking exclusive borrows of its environment. It rejects closures that only implementFnOnce
(i.e., consuming closures).F: Fn(...)
: This is the most restrictive bound. It only accepts closures implementingFn
, requiring that the closure can be called multiple times without mutation. It takes shared borrows of its environment. It rejects closures that only implementFnMut
orFnOnce
.
Choosing the right bound depends on how the function intends to use the closure: call once (FnOnce
), call multiple times with exclusive access (FnMut
), or call multiple times with shared access (Fn
).
Capture Examples:
-
Shared Borrow (
Fn
): The closure only reads captured data. It takes a shared borrow ofmessage
.fn main() { let message = String::from("Hello"); // Captures 'message' by shared reference. Implements Fn, FnMut, FnOnce. let print_message = || println!("{}", message); print_message(); print_message(); // Can call multiple times. println!("Original message still available: {}", message); // Still valid. }
-
Exclusive Borrow (
FnMut
): The closure modifies captured data. It takes an exclusive borrow ofcount
.fn main() { let mut count = 0; // Captures 'count' by exclusive ref.. Implements FnMut, FnOnce (but not Fn). let mut increment = || { count += 1; println!("Count is now: {}", count); }; increment(); // count becomes 1 increment(); // count becomes 2 // The exclusive borrow ends when 'increment' is no longer used. println!("Final count: {}", count); // Can access count again. }
-
Move (
FnOnce
): The closure takes ownership of captured data.fn main() { let data = vec![1, 2, 3]; // 'drop(data)' consumes data, so closure must take ownership. // Implements FnOnce only. let consume_data = || { println!("Data length: {}", data.len()); drop(data); // Moves ownership of 'data' into drop. }; consume_data(); // consume_data(); // Error: cannot call FnOnce closure twice (ownership was transf.). // println!("{:?}", data); // Error: 'data' was moved. }
12.2.1 The move
Keyword
Use move
before the parameter list (move || ...
) to force a closure to take ownership of all captured variables. This is vital when a closure must outlive its creation scope, like in threads, ensuring it owns its data rather than holding potentially dangling references.
use std::thread; fn main() { let data = vec![1, 2, 3]; // 'move' forces the closure to take ownership of 'data'. let handle = thread::spawn(move || { // 'data' is owned by this closure now. println!("Data in thread (length {}): {:?}", data.len(), data); // 'data' is dropped when the closure finishes. }); // println!("{:?}", data); // Error: 'data' was moved. handle.join().unwrap(); }
12.2.2 Closures as Function Parameters
Functions accepting closures use generic parameters with trait bounds (Fn
, FnMut
, FnOnce
) to specify requirements.
// Accepts any closure that takes an i32, returns an i32, // and can be called at least once. fn apply<F>(value: i32, op: F) -> i32 where F: FnOnce(i32) -> i32, // Most general bound that allows calling once { op(value) } // Accepts closures that can be called multiple times taking shared borrows. fn apply_repeatedly<F>(value: i32, op: F) -> i32 where F: Fn(i32) -> i32, // Requires only shared borrow { op(op(value)) // Call 'op' twice } fn main() { let double = |x| x * 2; // Implements Fn, FnMut, FnOnce println!("Apply once: {}", apply(5, double)); // Output: Apply once: 10 println!("Apply twice: {}", apply_repeatedly(5, double)); // Outp: Apply twice: 20 let data = vec![1]; let consume_and_add = |x| { // Implements FnOnce only drop(data); x + 1 }; println!("Apply consuming closure: {}", apply(5, consume_and_add)); // Output: 6 // apply_repeatedly(5, consume_and_add); // Error: 'Fn' bound not met (requires shared access, but this closure consumes) }
Choose the most restrictive bound needed: FnOnce
if called once (or consumes captured data), FnMut
if called multiple times with exclusive access (mutates captured data), Fn
if called multiple times with shared access (reads captured data).
12.2.3 Function Pointers vs. Closures
Regular functions (fn name(...)
) implicitly implement Fn*
traits if their signature matches. They can be passed where closures are expected, but cannot capture environment variables.
fn add_one(x: i32) -> i32 { x + 1 } fn apply<F>(value: i32, op: F) -> i32 where F: FnOnce(i32) -> i32, { op(value) } fn main() { let result = apply(10, add_one); // Pass the function 'add_one' println!("Result: {}", result); // Output: Result: 11 }