TL;DR: A closure in Rust is an anonymous function that can capture variables from the surrounding scope. They are defined with
|params| bodysyntax. Unlike regular functions, closures can borrow or take ownership of variables from their environment. The compiler automatically infers which capture mode is needed (&T,&mut T, orT) based on how the closure uses those variables. Closures implement one of three traits:Fn,FnMut, orFnOnce, determining how many times and in what way they can be called.
What Is a Closure in Rust?
A closure is an anonymous function defined inline that can capture variables from the enclosing scope, combining the flexibility of a function with access to local state.
fn main() {
let x = 5;
// Closure that captures x by reference
let add_x = |n| n + x;
println!("{}", add_x(10)); // 15
println!("{}", add_x(20)); // 25
println!("x is still: {}", x); // x is still accessible
}Closures are most commonly used with iterators (.map, .filter, .fold), thread spawning, and as callbacks. They are a zero-cost abstraction, the compiler monomorphizes each closure into a unique type with no runtime overhead.
How Do Closures Capture Variables?
Rust closures capture variables in the minimum way required: by immutable reference by default, by mutable reference if they mutate, and by value if forced with move or if the variable is needed after the closure's scope.
fn main() {
let name = String::from("Alice");
let count = 0;
// Captures `name` by &String (immutable reference)
let greet = || println!("Hello, {name}");
greet();
greet(); // can call multiple times; borrows each time
let mut counter = 0;
// Captures `counter` by &mut i32 (mutable reference)
let mut increment = || {
counter += 1;
counter
};
increment();
increment();
// `move`; takes ownership of `name`
let greeting = move || format!("Hi, {name}!");
// println!("{name}"); // COMPILE ERROR: name was moved into closure
}What Are Fn, FnMut, and FnOnce?
These three traits describe how a closure interacts with its captured values ; and therefore how it can be called.
| Trait | Called | Captures | Example |
|---|---|---|---|
FnOnce | Once | Takes ownership | Closures that consume captured values |
FnMut | Multiple times | Mutably borrows | Closures that mutate captured values |
Fn | Multiple times | Immutably borrows | Read-only closures |
Every closure implements at least FnOnce. If it doesn't consume its captures, it also implements FnMut. If it doesn't mutate them, it also implements Fn. The hierarchy is Fn ⊆ FnMut ⊆ FnOnce.
fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
f(f(x)) // called twice; needs Fn, not just FnOnce
}
fn apply_once<F: FnOnce(String) -> String>(f: F, s: String) -> String {
f(s) // called once; FnOnce is enough
}
fn main() {
let double = |x| x * 2;
println!("{}", apply_twice(double, 3)); // 12
let prefix = String::from("Hello, ");
let greet = move |name: String| prefix + &name; // consumes prefix
println!("{}", apply_once(greet, "world".to_string()));
}How Are Closures Used With Iterators?
Closures are the core of Rust's iterator API, .map(), .filter(), .fold(), and others all take closures, enabling expressive data transformation chains.
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers
.iter()
.filter(|&&x| x % 2 == 0) // keep even numbers
.map(|&x| x * x) // square them
.collect();
println!("{:?}", result); // [4, 16, 36]
// fold; reduce to single value
let sum: i32 = numbers.iter().fold(0, |acc, &x| acc + x);
println!("Sum: {sum}"); // 21
}Iterator chains with closures compile to the same machine code as hand-written loops, the closures are inlined and the chain is fused by the optimizer.
Frequently Asked Questions
A function pointer (fn(i32) -> i32) points to a named function and captures nothing. A closure is an anonymous function that can capture its environment. Closures implement Fn/FnMut/FnOnce; function pointers implement all three but only for functions with no captures. If you need to store a closure that may or may not capture state, use Box<dyn Fn(...)>.
Use a generic parameter bounded by the trait, or box it:
struct Callback<F: Fn(i32)> { handler: F } // generic; zero-cost
struct DynCallback { handler: Box<dyn Fn(i32)> } // dynamic; heap allocThe generic approach is preferred when the closure type is known at compile time.
std::thread::spawn requires 'static, the closure must own all its data because the thread may outlive the current scope. Add move to transfer ownership of captured variables into the closure.
Yes: async move || { ... } creates an async closure. This feature is stabilized in Rust 1.85+ via the async_closure feature. Before that, the common pattern was move || async move { ... }.
Sources
- The Rust Book ; Closures: Anonymous Functions that Capture Their Environment
- Rust Reference ; Closure expressions
Related Glossary Terms
- Trait:
Fn,FnMut, andFnOnceare traits - Ownership: Closures obey the same move/borrow rules as all Rust values
- Iterator: Closures are the primary way to use iterators
- Lifetime: Closures that borrow from the environment have implicit lifetimes
Keep Reading
- Rust Ownership and Borrowing Explained: closures capture variables by reference or by move
- Rust Generics and Traits Explained: Fn, FnMut, and FnOnce are the traits behind closures
- Rust for Python Developers: Python lambdas vs Rust closures
