Reading — step 1 of 6
Learn
Closures are anonymous functions that can capture variables from their environment. Rust's three closure traits (Fn, FnMut, FnOnce) categorize closures by HOW they use captured variables.
Basic closure syntax
let add = |a, b| a + b;
println!("{}", add(3, 4)); // 7
// Multi-statement body uses braces:
let greet = |name: &str| {
let s = format!("Hi, {}", name);
println!("{}", s);
};
greet("Ada");
Types are usually inferred. Annotate when the compiler complains.
Capturing by reference, mutable reference, or move
Closures capture as little as needed:
let message = String::from("hi");
let print_msg = || println!("{}", message); // borrows message (&)
print_msg();
println!("{}", message); // ✓ still usable
let mut counter = 0;
let mut inc = || counter += 1; // borrows counter mutably (&mut)
inc();
inc();
println!("{}", counter); // 2
let owned = String::from("owned");
let take = move || println!("{}", owned); // takes ownership (move)
take();
// println!("{}", owned); // ✗ moved
move forces the closure to take ownership — useful for spawning threads where the closure outlives the caller's scope.
The three closure traits
Rust has three traits for closures, hierarchical:
FnOnce— can be called at least once. Closures that consume captured values implement this.FnMut— can be called many times, may mutate captured values. ImpliesFnOnce.Fn— can be called many times without mutation. ImpliesFnMutandFnOnce.
The compiler picks the most permissive that works for your closure.
fn apply<F: Fn() -> i32>(f: F) -> i32 { f() }
fn apply_mut<F: FnMut() -> i32>(mut f: F) -> i32 { f() }
fn apply_once<F: FnOnce() -> i32>(f: F) -> i32 { f() }
The hierarchy runs one way: every Fn closure also implements FnMut and FnOnce. So a function whose parameter is bound by FnOnce accepts ALL closures, while an Fn bound is the strictest — it only accepts closures that never mutate or consume their captures. When you write an API, ask for the most permissive trait you can (FnOnce if you call it once).
Returning closures
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |y| x + y
}
let add5 = make_adder(5);
println!("{}", add5(10)); // 15
impl Fn(...) -> ... is the modern syntax. Use Box<dyn Fn(...) -> ...> if you need to RETURN different closure types from one function.
Closures vs functions
fn double(x: i32) -> i32 { x * 2 }
let double_closure = |x: i32| x * 2;
// Both work in higher-order contexts:
[1, 2, 3].iter().map(|&n| double(n));
[1, 2, 3].iter().map(|&n| double_closure(n));
Closures + iterators are the bread and butter of Rust data processing — you'll see them everywhere.
Common mistakes
- Calling a
FnOnceclosure twice — compile error. - Forgetting
movewhen spawning threads — closure can't outlive the captured borrows. - Returning a non-
'staticclosure — compile error if it captures non-'static borrows. - Mutating a captured binding without
&mut— closure must be markedFnMut.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…