Closures & Fn Traits
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Rust 1.99.0 and the 2024 edition, checked on 2026-10-06. A closure is an anonymous
function that can capture variables from the scope where it is defined. Closures are everywhere in idiomatic Rust:
iterator adapters, sorting keys, callbacks, thread and task bodies, lazy defaults (unwrap_or_else). Each closure has
its own unique, unnameable type, and implements one or more of the Fn, FnMut and FnOnce traits depending on what
it does with its captures.
Syntax and type inference
Parameters go between pipes; the body is an expression, so braces are only needed for several statements. Parameter and return types are usually inferred from the first use:
fn main() {
let add_tax = |cents: u32| -> u32 { cents * 121 / 100 }; // fully annotated
let double = |x| x * 2; // inferred from use below
let greet = || println!("welcome to the shelf"); // no parameters
println!("{} {}", add_tax(1000), double(21));
greet();
let mut titles = vec!["Emma", "Dune", "Beloved"];
titles.sort_by_key(|t| t.len()); // closure as an argument
println!("{titles:?}");
}
Source: The Book: Closures
Capturing
A closure captures each variable it mentions in the least demanding way the body allows:
-
by shared reference (
&T) if it only reads the variable; -
by mutable reference (
&mut T) if it modifies it; -
by value (move) if it moves the variable out (for example, returns it or passes it to a function taking ownership).
The usual borrowing rules apply while the closure exists: a closure that borrows count mutably blocks other uses of
count until its last use.
fn main() {
let catalogue = vec!["Dune", "Emma"];
let count = || catalogue.len(); // captures &catalogue
println!("{} {:?}", count(), catalogue); // reading catalogue too is fine
let mut lent = 0;
let mut lend = || lent += 1; // captures &mut lent; the binding must be `mut`
lend();
lend();
println!("lent {lent}"); // the mutable borrow has ended
let title = String::from("Kindred");
let consume = || drop(title); // captures title by value
consume();
// consume(); // would not compile: closure moved `title` already (E0382)
}
move closures
move forces every capture to be by value, whatever the body does. It is required whenever the closure may outlive
the current scope — returned from a function, sent to another thread, or spawned as an async task — because borrows of
local variables would dangle. Without it, the compiler reports
E0373 ("closure may outlive the current function, but it borrows
x`") and suggests adding `move.
use std::thread;
fn make_counter() -> impl FnMut() -> u32 {
let mut n = 0;
move || { // n moves into the closure; it lives as long as the closure
n += 1;
n
}
}
fn main() {
let mut next_id = make_counter();
println!("{} {}", next_id(), next_id()); // 1 2
let titles = ["Dune".to_string(), "Emma".to_string()];
let handle = thread::spawn(move || titles.len()); // titles moves into the thread
println!("{}", handle.join().unwrap());
let shared = std::sync::Arc::new(String::from("catalog"));
let s2 = std::sync::Arc::clone(&shared); // clone *before* the move
thread::spawn(move || println!("{s2}")).join().unwrap();
println!("{shared}");
}
Moving a Copy value (such as an integer) copies it, so the original stays usable. To move some variables and borrow
others, create references beforehand (let r = &big; move || r.len()) — the reference itself is what gets moved.
Disjoint field capture (2021 edition)
Since the 2021 edition, closures capture paths such as book.title rather than the whole book. Two closures can
then borrow different fields at the same time, and a move closure that uses one field only moves that field:
struct Book {
title: String,
loans: u32,
}
fn main() {
let mut book = Book { title: "Dune".into(), loans: 0 };
let mut record = || book.loans += 1; // captures &mut book.loans only
let label = &book.title; // still allowed: a different field
record();
println!("{label} lent {} times", book.loans);
}
Fn, FnMut and FnOnce
Every closure implements one or more of three traits, determined by what its body does with its captures — not by how they are captured:
| Trait | Call signature | Implemented when the body … |
|---|---|---|
|
|
moves something out of its captures (all closures implement it) |
|
|
mutates captures but does not move them out |
|
|
only reads its captures (or captures nothing) |
Every Fn is also FnMut, and every FnMut is also FnOnce, so a function asking for FnOnce accepts any closure.
When accepting a closure, ask for the least restrictive trait that works: FnOnce if you call it once (like
Option::unwrap_or_else), FnMut if you call it repeatedly without sharing (like Iterator::for_each), Fn if you
need shared or concurrent calls.
fn call_twice<F: FnMut() -> u32>(mut f: F) -> u32 {
f() + f()
}
fn call_once<F: FnOnce() -> String>(f: F) -> String {
f()
}
fn apply_to_all<F: Fn(&str) -> usize>(items: &[&str], f: F) -> Vec<usize> {
items.iter().map(|s| f(s)).collect()
}
fn main() {
let mut n = 0;
println!("{}", call_twice(|| { n += 1; n })); // FnMut: 1 + 2 = 3
let title = String::from("Dune");
println!("{}", call_once(move || title)); // FnOnce: returns the captured String
let prefix = 3;
println!("{:?}", apply_to_all(&["Dune", "Middlemarch"], |s| s.len() + prefix)); // Fn
}
Passing a closure that does not implement the required trait is error
E0525 ("expected a closure that implements the Fn trait, but this
closure only implements `FnOnce`"), and the message points to the capture that is moved or mutated.
Closures as parameters and return values
Because each closure has a unique type, functions take them through generics or impl Trait, or through a trait object
when different closures must share one type (stored in a collection or a struct field):
type Rule = Box<dyn Fn(&str) -> bool>;
fn starts_with(prefix: &'static str) -> impl Fn(&str) -> bool {
move |s| s.starts_with(prefix)
}
fn make_rules(min_len: usize) -> Vec<Rule> {
vec![
Box::new(move |s: &str| s.len() >= min_len),
Box::new(starts_with("978")),
Box::new(|s: &str| s.bytes().all(|b| b.is_ascii_digit())),
]
}
fn main() {
let rules = make_rules(13);
for isbn in ["9780441013593", "978-0441", "1234567890123"] {
let ok = rules.iter().all(|rule| rule(isbn));
println!("{isbn}: {ok}");
}
}
Source: The Book: Returning Closures
impl Fn(...) is static dispatch with no allocation; Box<dyn Fn(...)> erases the type at the cost of a heap
allocation and an indirect call. Add + Send + Sync + 'static to boxed closures that cross threads.
Function pointers
Plain functions and closures that capture nothing coerce to function pointers of type fn(Args) -> Ret. Function
pointers implement all three Fn traits, so a function name can be passed wherever a closure is expected — often
shorter than wrapping it in a closure:
fn main() {
let titles = ["dune", "emma"];
let upper: Vec<String> = titles.iter().map(|t| t.to_uppercase()).collect();
let lengths: Vec<usize> = titles.iter().copied().map(str::len).collect(); // a method as a function
let parsed: Vec<Option<u16>> = ["1965", "x"].iter().map(|s| s.parse().ok()).collect();
let as_some: Vec<Option<&str>> = titles.iter().copied().map(Some).collect(); // a variant constructor
let op: fn(u32, u32) -> u32 = |a, b| a + b; // non-capturing closure coerced to fn
println!("{upper:?} {lengths:?} {parsed:?} {as_some:?} {}", op(2, 3));
}
Source: The Book: Function Pointers
Function pointers matter mainly for FFI callbacks and for tables of functions; see Functions & Expressions.
Async closures (1.85)
Rust 1.85 stabilised async closures, async |args| { ... }, and the matching traits AsyncFn, AsyncFnMut and
AsyncFnOnce. Unlike a closure returning an async block (|x| async move { ... }), an async closure’s future can
borrow from the closure’s captures, and the traits make "a function that takes an async callback" easy to write:
async fn retry<F>(times: u32, op: F) -> Result<String, String>
where
F: AsyncFn(u32) -> Result<String, String>, // a bound written like Fn
{
let mut last = Err("never ran".to_string());
for attempt in 1..=times {
last = op(attempt).await;
if last.is_ok() {
break;
}
}
last
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
let service = String::from("catalog");
let result = retry(3, async |attempt| {
// borrows `service` from the closure's captures across the await point
tokio::task::yield_now().await;
if attempt < 3 { Err(format!("{service} busy")) } else { Ok(format!("{service} ok on try {attempt}")) }
})
.await;
println!("{result:?}");
}
AsyncFn* and Future/IntoFuture are in the 2024 prelude. As with async fn in traits, there is no syntax yet to
require the returned future to be Send; for code that spawns the future on a multi-threaded runtime, the older
F: Fn(T) -> Fut, Fut: Future<Output = R> + Send form is still needed. See
Async/Await & Futures.
Common errors
| Error | Cause and fix |
|---|---|
E0373 closure may outlive the current function |
add |
E0525 expected |
the closure moves a capture
out; clone inside the closure, or relax the bound to |
E0596 cannot borrow as mutable |
a closure that mutates its captures
must be bound with |
E0308 mismatched types / "no two closures have the same type" |
store different closures in a |
E0499/E0502 while a closure is alive |
the closure holds a borrow; end its last use first, or use |
References
-
The Rust Programming Language: Advanced Functions and Closures
-
Rust by Example: Closures (capturing, as input parameters, type anonymity, input functions, as output parameters)