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.

  1. FnOnce: Consumes captured variables. Can be called only once. All closures implement this.
  2. FnMut: Mutably borrows captured variables. Can be called multiple times, modifying the environment. Implies FnOnce.
  3. Fn: Immutably borrows captured variables. Can be called multiple times without side effects on the environment. Implies FnMut and FnOnce.

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
}