Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 implementing FnOnce can be called at least once, potentially consuming (moving) its captured environment in the process. All closures implicitly implement FnOnce.
  • FnMut: Closures implementing FnMut can be called multiple times and can mutate their captured environment through exclusive borrows. They do not consume the environment. All Fn closures also implement FnMut.
  • 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 implementing Fn 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:

  1. F: FnOnce(...): This is the most permissive (least restrictive) bound. It accepts any closure matching the signature (Fn, FnMut, or FnOnce), as it only requires the closure to be callable at least once.
  2. F: FnMut(...): This bound is more restrictive. It accepts closures implementing FnMut or Fn (since Fn implies FnMut), requiring the closure to be callable multiple times, potentially taking exclusive borrows of its environment. It rejects closures that only implement FnOnce (i.e., consuming closures).
  3. F: Fn(...): This is the most restrictive bound. It only accepts closures implementing Fn, requiring that the closure can be called multiple times without mutation. It takes shared borrows of its environment. It rejects closures that only implement FnMut or FnOnce.

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 of message.

    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 of count.

    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
}