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

  1. 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.

  2. FnMut
    A closure that mutably borrows captured variables. This allows repeated calls that can modify the captured data.

  3. 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
}