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.