Frontend Labs
⋯

20. Closures: Fn, FnMut, FnOnce

A closure is an anonymous struct holding its captured variables, plus an implementation of one of three traits, chosen by how the body uses its captures:

Trait Body does Callable Example
Fn reads captures many times, also concurrently `
FnMut mutates captures many times, one call at a time `
FnOnce moves a capture out once `
fn apply_twice(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(f(x)) }
fn run_n(mut f: impl FnMut(), n: usize) { for _ in 0..n { f(); } }
fn run_once(f: impl FnOnce() -> String) -> String { f() }

fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n // `move`: the closure takes ownership of `n` (here a copy)
}

fn main() {
    let offset = 10;
    println!("{}", apply_twice(|x| x + offset, 1)); // 21

    let mut count = 0;
    run_n(|| count += 1, 3);
    println!("count={count}"); // 3

    let greeting = String::from("hi");
    println!("{}", run_once(move || greeting + " there")); // consumes `greeting`

    let add5 = make_adder(5);
    let handlers: Vec<Box<dyn Fn(i32) -> i32>> = vec![Box::new(add5), Box::new(|x| x * 2)];
    println!("{:?}", handlers.iter().map(|h| h(10)).collect::<Vec<_>>()); // [15, 20]
}

move closures take ownership of what they capture. You need them whenever the closure outlives the current stack frame: in threads, tasks, returned closures, and stored callbacks. Without move, a closure captures by reference. That is usually what you want locally, and it is rejected for spawned work.

Compare Go §5.4: Go closures always capture by reference and the GC keeps captured variables alive, which is how Go got its loop-variable capture bug before 1.22. Rust forces you to choose between borrowing and moving, and checks the choice.


Generated from real-life-rust-guide.md · 8 Oct 2026 16:36