Can you write this from memory?
Define a closure `add_one` that takes an i32 and returns it plus 1.
Rust closures are anonymous functions that capture their environment. They're everywhere—iterators, callbacks, thread spawning.
The tricky part is understanding how closures capture variables: by reference, mutable reference, or by value. This determines which trait they implement (Fn, FnMut, or FnOnce) and where they can be used.
This page focuses on closure syntax and capture semantics.
The most common closure error. std::thread::spawn requires FnOnce + Send + 'static, so the closure must own everything it captures:
// WRONG: closure borrows, but data won't live long enough
// let handle = {
// let name = String::from("Alice");
// std::thread::spawn(|| println!("{name}")) // error[E0373]
// };
// RIGHT: move ownership into the closure
let handle = {
let name = String::from("Alice");
std::thread::spawn(move || println!("{name}"))
};
handle.join().unwrap();
The move keyword forces the closure to take ownership of all captured variables, even if a borrow would suffice for the closure body. This satisfies the 'static lifetime bound. If you're coming from Python, closures work quite differently; see Python vs Rust functions compared for a side-by-side breakdown.
Without move, closures capture by the least restrictive mode the body requires:
- Immutable borrow (
&T) — if the closure only reads the value - Mutable borrow (
&mut T) — if the closure mutates the value - Move (
T) — if the closure consumes the value (e.g., drops it, returns it, moves it into another binding)
move overrides this: every capture becomes by-value. For Copy types, that means a copy. For owned types, that means a move.
let x = 10; // Copy type
let s = String::from("hi"); // non-Copy type
let f = move || {
println!("{x}"); // x is copied in
println!("{s}"); // s is moved in
};
f();
println!("{x}"); // fine — x was copied
// println!("{s}"); // ERROR: s was moved into the closure
A common misconception. The move keyword controls how values get into the closure. The Fn/FnMut/FnOnce trait depends on what the closure body does with those values:
let name = String::from("Alice");
// move + only reads capture → implements Fn (callable many times)
let greet = move || println!("Hello, {name}");
greet();
greet(); // fine — name is read, not consumed
// move + consumes capture → implements FnOnce (callable once)
let consume = move || {
drop(name); // name is consumed
};
consume();
// consume(); // ERROR: cannot call FnOnce again
Every closure implements one or more of these traits. The hierarchy: Fn ⊂ FnMut ⊂ FnOnce.
| Trait | Body does | Can call |
|---|---|---|
Fn | Reads captures | Many times |
FnMut | Mutates captures | Many times |
FnOnce | Consumes captures | Once |
// Fn: borrows immutably, callable many times
let x = 10;
let add_x = |n| n + x;
println!("{} {}", add_x(1), add_x(2));
// FnMut: borrows mutably, callable many times
let mut count = 0;
let mut inc = || count += 1;
inc();
inc();
// FnOnce: takes ownership, callable once
let name = String::from("Alice");
let greet = || {
let owned = name; // moves name out — consumes it
println!("Hello, {owned}");
};
greet();
// greet(); // ERROR: cannot call again, name was consumed
This happens when a function expects a reusable closure but your closure consumes a captured value:
// WRONG: closure moves name out, so it's FnOnce
// fn repeat(f: impl Fn()) {
// f(); f();
// }
// let name = String::from("Alice");
// repeat(|| {
// let owned = name; // error[E0525]
// println!("{owned}");
// });
// RIGHT: borrow instead of consuming
fn repeat(f: impl Fn()) {
f(); f();
}
let name = String::from("Alice");
repeat(|| {
println!("{name}"); // borrows — now it's Fn
});
If the closure must consume, change the bound to FnOnce or .clone() before capture.
Both the captured variable and the closure binding need mut:
// WRONG: variable and closure not declared mut
// let count = 0;
// let inc = || count += 1; // error[E0596]
// inc(); // error: inc is not mut
// RIGHT: both bindings are mut
let mut count = 0;
let mut inc = || count += 1;
inc();
inc();
println!("{count}"); // 2
Iterator adapters are where closures shine:
let nums = vec![1, 2, 3, 4, 5];
// map: transform each element
let doubled: Vec<i32> = nums.iter().map(|&x| x * 2).collect();
// filter: keep elements matching a predicate
let evens: Vec<&i32> = nums.iter().filter(|&&x| x % 2 == 0).collect();
// fold: accumulate into a single value
let sum = nums.iter().fold(0, |acc, x| acc + x);
// chaining: filter then map
let big_doubled: Vec<i32> = nums.iter()
.filter(|&&x| x > 2)
.map(|&x| x * 2)
.collect();
Closures that capture nothing can coerce to function pointers (fn). Closures that capture cannot:
// Works: no captures, coerces to fn
let add: fn(i32, i32) -> i32 = |a, b| a + b;
// ERROR: captures x, cannot coerce to fn
// let x = 10;
// let add_x: fn(i32) -> i32 = |n| n + x;
// RIGHT: use impl Fn or generics instead
fn apply(f: impl Fn(i32) -> i32, n: i32) -> i32 { f(n) }
let x = 10;
let result = apply(|n| n + x, 5); // 15
If an API requires fn(), make sure your closure captures nothing. If it must capture, the API needs to accept impl Fn() or Box<dyn Fn()>. The traits and generics guide covers how Fn, FnMut, and FnOnce work as trait bounds in generic functions.
Since edition 2021, closures capture individual fields instead of the whole struct:
struct Config { name: String, retries: u32 }
let config = Config { name: "app".into(), retries: 3 };
// Rust 2021: captures only config.retries, not all of config
let get_retries = || config.retries;
// config.name is still usable here
println!("{}", config.name);
In edition 2018, this would fail because the closure captured all of config. If you need the old behavior, use move or capture a reference to the whole struct explicitly. Closures accepting trait-bounded behaviors enable the Strategy design pattern -- select an algorithm at runtime by passing different closures.
- The Rust Book: Closures — capture modes, Fn traits, iterator patterns
- Rust Reference: Closure Types — precise capture rules
- Rust Edition Guide: Disjoint Capture — 2021 field-level captures
- Advanced Functions & Closures — function pointers, returning closures
When to Use Rust Closures: Fn, FnMut, FnOnce & Captures
- Use closures with iterator adapters (map, filter, fold).
- Use `move` closures when spawning threads or returning closures.
- Use `Fn` bounds when you need to call a closure multiple times.
- Use `FnOnce` bounds when the closure only needs to run once.
- Prefer closures over named functions for short, localized logic.
Check Your Understanding: Rust Closures: Fn, FnMut, FnOnce & Captures
What is the difference between Fn, FnMut, and FnOnce?
FnOnce can be called once (consumes captured values). FnMut can be called multiple times but may mutate captures. Fn can be called multiple times without mutation. Every Fn is FnMut, every FnMut is FnOnce.
What You'll Practice: Rust Closures: Fn, FnMut, FnOnce & Captures
Common Rust Closures: Fn, FnMut, FnOnce & Captures Pitfalls
- You get `closure may outlive the current function` when spawning a thread → the closure borrows a local variable that will be dropped when the function returns → add `move` before the closure: `std::thread::spawn(move || ...)`.
- You get `cannot borrow as mutable, as it is not declared as mutable` → the closure mutates a captured variable but the bindings are not `mut` → declare both the variable and the closure as mutable: `let mut count = 0; let mut inc = || count += 1;`.
- You get `expected Fn, but this closure only implements FnOnce` → the closure consumes a captured value so it can only run once → borrow instead of moving, or `.clone()` the value before capture.
- `move` forces ownership transfer of everything the closure mentions, which can force cloning later or prevent reuse → prefer borrowing with `&vec` when the closure doesn't need to own the data, or capture an `Arc` when multiple owners are needed.
- You pass a closure where a function pointer is expected and get a type error → closures that capture variables cannot coerce to `fn()` → use a closure that captures nothing, or change the signature to accept `impl Fn()` instead.
Rust Closures: Fn, FnMut, FnOnce & Captures FAQ
When do I need the move keyword?
Use `move` when the closure needs to own its captured variables—typically when returning a closure, spawning threads, or when the closure outlives the current scope.
How does Rust decide how to capture variables?
Rust infers the least restrictive capture mode needed: immutable borrow if possible, mutable borrow if mutation is needed, ownership if the value is consumed. Use `move` to force ownership.
Can closures have explicit type annotations?
Yes: `|x: i32| -> i32 { x + 1 }`. Usually unnecessary since types are inferred from context, but sometimes needed for clarity or when inference fails.
What is the type of a closure?
Each closure has a unique, anonymous type. You cannot name it directly. Use `impl Fn(...)` for return types or generics with `Fn` bounds for parameters.
Why use closures instead of functions?
Closures capture their environment. Functions cannot. Use closures when you need access to surrounding variables. Use functions when you want reusable, named logic.
Rust Closures: Fn, FnMut, FnOnce & Captures Syntax Quick Reference
let add = |a, b| a + b;let f = |x| {
let y = x + 1;
y * 2
};let f = |x: i32| -> i32 { x + 1 };let x = 5;
let f = || x + 1;let s = String::from("hi");
let f = move || println!("{}", s);let mut count = 0;
let mut inc = || count += 1;nums.iter().map(|x| x * 2)nums.iter().filter(|x| *x > 0)fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32Rust Closures: Fn, FnMut, FnOnce & Captures Sample Exercises
Fill in how to call the closure with 5.
doubleFill in the closure syntax for a closure with no parameters.
||Fill in the return expression for a closure that doubles `x` and adds 1.
doubled + 1+ 50 more exercises