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:
- By shared reference if the closure only reads the variable.
- By mutable reference if it changes it.
- 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
- A mutating closure stored without
mut: E0596, "cannot borrowbumpas mutable, as it is not declared as mutable". Writelet mut bump = || .... - Using a captured variable while the closure still holds a mutable borrow: E0502 or E0499. Call the closure for the last time first.
- Passing an
FnMutclosure whereFnis required: E0596, "cannot borrowlogas mutable, as it is a captured variable in aFnclosure". Change the bound toFnMut. - Moving out of a capture in an
Fnclosure: E0507, "cannot move out ofname, a captured variable in anFnclosure". Clone inside, or acceptFnOnce. - Forgetting
movefor a thread: E0373, "closure may outlive the current function".
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)]