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, mutates, or only reads its environment. Functions accepting closures use these traits as bounds.
Every closure implements at least FnOnce
. If it doesn’t move captured variables out, it also implements FnMut
. If it only needs immutable access (or captures nothing), it also implements Fn
.
FnOnce
: Consumes captured variables. Can be called only once. All closures implement this.FnMut
: Mutably borrows captured variables. Can be called multiple times, modifying the environment. ImpliesFnOnce
.Fn
: Immutably borrows captured variables. Can be called multiple times without side effects on the environment. ImpliesFnMut
andFnOnce
.
The compiler selects the least restrictive trait (Fn
< FnMut
< FnOnce
) needed by the closure’s body.
Capture Examples:
-
Immutable Borrow (
Fn
): The closure only reads captured data.fn main() { let message = String::from("Hello"); // Borrows 'message' immutably. 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. }
-
Mutable Borrow (
FnMut
): The closure modifies captured data.fn main() { let mut count = 0; // Borrows 'count' mutably. 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 mutable 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. // 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 without mutation. fn apply_repeatedly<F>(value: i32, op: F) -> i32 where F: Fn(i32) -> i32, // Requires immutable borrow or no capture { 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 }
Choose the most restrictive bound needed: FnOnce
if called once, FnMut
if called multiple times with mutation, Fn
if called multiple times without mutation.
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 }