12.3 Closure Traits: FnOnce
, FnMut
, and Fn
Closures are categorized by the way they capture variables. Each closure implements one or more of these traits:
FnOnce
: Takes ownership of captured variables; can be called once.FnMut
: Captures by mutable reference, allowing mutation of captured variables; can be called multiple times.Fn
: Captures by immutable reference only; can be called multiple times without mutating or consuming the environment.
12.3.1 The Three Closure Traits
-
FnOnce
A closure that consumes variables from the environment. After it runs, the captured variables are no longer available elsewhere because the closure has taken ownership. -
FnMut
A closure that mutably borrows captured variables. This allows repeated calls that can modify the captured data. -
Fn
A closure that immutably borrows or doesn’t need to borrow at all. It can be called repeatedly without altering the environment.
12.3.2 Capturing the Environment
Depending on how a closure uses the variables it captures, Rust automatically assigns one or more of the traits above:
By Immutable Reference (Fn
)
fn main() { let x = 10; let print_x = || println!("x is {}", x); print_x(); print_x(); // Allowed multiple times (immutable borrow) }
By Mutable Reference (FnMut
)
fn main() { let mut x = 10; let mut add_to_x = |y| x += y; add_to_x(5); add_to_x(2); println!("x is {}", x); // 17 }
By Ownership (FnOnce
)
fn main() { let x = vec![1, 2, 3]; let consume_x = || drop(x); consume_x(); // consume_x(); // Error: x was moved }
12.3.3 The move
Keyword
Use move
to force a closure to take ownership of its environment:
fn main() { let x = vec![1, 2, 3]; let consume_x = move || println!("x is {:?}", x); consume_x(); // println!("{:?}", x); // Error: x was moved }
This is vital when creating threads, where the closure must outlive its original scope by moving all required data.
12.3.4 Passing Closures as Arguments
Functions that accept closures usually specify a trait bound like FnOnce
, FnMut
, or Fn
:
fn apply_operation<F, T>(value: T, func: F) -> T
where
F: FnOnce(T) -> T,
{
func(value)
}
Example Usage
fn main() { let value = 5; let double = |x| x * 2; let result = apply_operation(value, double); println!("Result: {}", result); // 10 } fn apply_operation<F, T>(value: T, func: F) -> T where F: FnOnce(T) -> T, { func(value) }
12.3.5 Using Functions Where Closures Are Expected
A free function (e.g., fn(i32) -> i32
) implements these closure traits if its signature matches:
fn main() { let result = apply_operation(5, double); println!("Result: {}", result); // 10 } fn double(x: i32) -> i32 { x * 2 } fn apply_operation<F>(value: i32, func: F) -> i32 where F: FnOnce(i32) -> i32, { func(value) }
12.3.6 Generic Closures vs. Generic Functions
Closures do not declare their own generic parameters, but you can wrap them in generic functions:
use std::ops::Add; fn add_one<T>(x: T) -> T where T: Add<Output = T> + From<u8>, { x + T::from(1) } fn main() { let result_int = add_one(5); // i32 let result_float = add_one(5.0); // f64 println!("int: {}, float: {}", result_int, result_float); // 6, 6.0 }