RustArena

Closures in Rust and the Fn, FnMut and FnOnce traits

How Rust closures capture variables, the difference between Fn, FnMut and FnOnce, the move keyword, and passing or returning closures.

A closure is an anonymous function you can store in a variable or pass to another function. Unlike a normal fn, it can use variables from the scope where it's created; this is called capturing them.

fn main() {
    let tax_rate = 0.2;
    let with_tax = |price: f64| price * (1.0 + tax_rate);
    println!("{:.2}", with_tax(10.0));

    let double = |x| x * 2;
    println!("{}", double(21));
}
12.00
42

Parameters go between | bars; the body is an expression (use { } for several statements). Types are usually inferred, but each closure gets one set of types: after double(21), calling double(1.5) is E0308, mismatched types.

How closures capture

The compiler picks the least powerful capture that works:

  1. By shared reference if the closure only reads the variable.
  2. By mutable reference if it changes it.
  3. By value (move) if it consumes it (moves it somewhere, drops it).

The kind of capture decides which Fn traits the closure implements:

Trait The closure... Can be called Example
Fn only reads its captures many times, even concurrently |x| x * factor
FnMut changes its captures many times, needs mut || count += 1
FnOnce consumes a capture once || drop(buffer)

Every Fn is also FnMut and FnOnce, so a function asking for FnOnce accepts anything; one asking for Fn accepts the fewest closures.

The move keyword

move |...| ... forces the closure to take ownership of everything it captures, even if it only reads it. You need it when the closure outlives the current scope: threads (thread::spawn(move || ...)), async tasks, and closures you return. move doesn't change which Fn trait the closure implements; it changes where the data lives.

Taking closures as parameters

fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(x) }      // same thing
fn retry<F: FnMut() -> bool>(mut attempt: F) { ... }           // stateful callback

Pick the most permissive bound that your function's use allows: FnOnce if you call it once, FnMut if you call it repeatedly, Fn only if you need to call it through a shared reference or from several threads.

Returning closures

Each closure has its own unnamed type, so you return it as impl Fn(...), or as Box<dyn Fn(...)> when different branches return different closures:

fn make_multiplier(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x * n
}

Common mistakes

Closures shine with iterator adapters and sorting: v.sort_by_key(|p| p.age), v.retain(|x| *x > 0), items.iter().map(|i| i.price).sum().

Example

fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(f(x))
}

fn run_n<F: FnMut()>(n: usize, mut f: F) {
    for _ in 0..n {
        f();
    }
}

fn consume<F: FnOnce() -> String>(f: F) -> String {
    f()
}

fn make_multiplier(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x * n
}

fn make_op(name: &str) -> Box<dyn Fn(i32, i32) -> i32> {
    match name {
        "add" => Box::new(|a, b| a + b),
        "max" => Box::new(|a, b| a.max(b)),
        _ => Box::new(|_, _| 0),
    }
}

fn main() {
    let bonus = 5;
    println!("{}", apply_twice(|x| x + bonus, 10));

    let mut hits = Vec::new();
    run_n(3, || hits.push(hits.len() * 10));
    println!("{hits:?}");

    let report = String::from("all systems go");
    println!("{}", consume(move || report + "!"));

    let triple = make_multiplier(3);
    println!("{} {}", triple(7), make_multiplier(10)(4));
    println!("{} {}", make_op("add")(2, 3), make_op("max")(2, 3));

    let mut players = vec![("Ferris", 31), ("Corro", 12), ("Gopher", 25)];
    let min_score = 20;
    players.retain(|(_, score)| *score >= min_score);
    players.sort_by_key(|(name, _)| *name);
    println!("{players:?}");
}

Output:

20
[0, 10, 20]
all systems go!
21 40
5 3
[("Ferris", 31), ("Gopher", 25)]

Practice it