Closures
What Is a Closure?
Section titled “What Is a Closure?”A closure is an anonymous function defined inline with the |params| body syntax. Unlike a regular fn, a closure can capture variables from its surrounding scope.
let multiplier = 3;let triple = |x| x * multiplier; // captures multiplier from the environmentprintln!("{}", triple(7)); // 21Capturing the Environment
Section titled “Capturing the Environment”Rust has three closure traits that describe how a closure uses its captured values:
| Trait | How it captures | Can be called |
|---|---|---|
Fn | Borrows immutably (&T) | Any number of times |
FnMut | Borrows mutably (&mut T) | Any number of times |
FnOnce | Takes ownership (T) | Exactly once |
Every closure implements at least FnOnce. If it only borrows, it also implements Fn. The compiler infers which trait(s) a closure implements by looking at what its body actually does with each captured variable — never by looking at how you intend to use it. That inference is not a detail you can ignore, because the moment you use the closure somewhere that requires a stronger guarantee than the body earns, the compiler will say so.
flowchart TD
A[What does the closure body do with a captured variable?] --> B{Only reads it?}
B -->|Yes| C[Fn: borrows &T]
B -->|No, mutates it| D{Only mutates, never consumes?}
D -->|Yes| E[FnMut: borrows &mut T]
D -->|No, moves or drops it| F[FnOnce: takes ownership T] Fn — Immutable Borrow
Section titled “Fn — Immutable Borrow”fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
let base = 10;let add_base = |x| x + base; // borrows base immutablyprintln!("{}", apply(add_base, 5)); // 15FnMut — Mutable Borrow
Section titled “FnMut — Mutable Borrow”let mut count = 0;let mut increment = || { count += 1; count };println!("{}", increment()); // 1println!("{}", increment()); // 2drop(increment); // release the mutable borrowprintln!("count is {}", count); // 2drop(increment) must be called before reading count directly, because increment holds a &mut count that would conflict with the immutable borrow in println!.
FnOnce — Takes Ownership
Section titled “FnOnce — Takes Ownership”let name = String::from("Rust");let greet = || println!("Hello, {}!", name); // moves name into closuregreet(); // name is consumed here// greet(); // would compile-error — FnOnce can only be called onceWhat the Compiler Says: A Closure That Consumes Can’t Be Called Twice
Section titled “What the Compiler Says: A Closure That Consumes Can’t Be Called Twice”Fn is a supertrait requirement: anything that requires Fn needs to call the closure as many times as it wants, so a closure whose body actually consumes a captured value — even just by calling drop() on it — can only ever be FnOnce, and using it where Fn is required is a type error, not a runtime surprise:
// @expect-error E0525fn call_twice<F: Fn()>(f: F) { f(); f();}
fn main() { let name = String::from("Rust"); // this closure takes ownership of `name` and consumes it inside — FnOnce only let greet = move || { println!("bye, {}", name); drop(name); }; call_twice(greet); // ERROR: call_twice requires Fn, greet is only FnOnce}error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce` --> src/main.rs:9:17 | 9 | let greet = move || { | ^^^^^^^ this closure implements `FnOnce`, not `Fn`10 | println!("bye, {}", name);11 | drop(name); | ---- closure is `FnOnce` because it moves the variable `name` out of its environment12 | };13 | call_twice(greet); // ERROR: call_twice requires Fn, greet is only FnOnce | ---------- ----- the requirement to implement `Fn` derives from here | | | required by a bound introduced by this call |note: required by a bound in `call_twice` --> src/main.rs:1:18 | 1 | fn call_twice<F: Fn()>(f: F) { | ^^^^ required by this bound in `call_twice`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0525`.Notice the diagnostic points at the exact line (drop(name)) that downgrades the closure to FnOnce — the compiler is not guessing, it derived this from the closure body. Removing the drop(name) call (just printing name without consuming it) would make the closure Fn and let it compile.
The move Keyword
Section titled “The move Keyword”move forces the closure to take ownership of all captured variables, regardless of whether it would otherwise only borrow:
let message = String::from("hi");let say = move || println!("{}", message); // message moved into say// println!("{}", message); // error — message was movedsay();move is essential when the closure outlives its defining scope (e.g., passing it to a thread). Note that move alone does not force FnOnce — the greet closure above needed move and an internal drop(name) to become FnOnce; a move closure that only reads its captured String is still Fn, because Fn/FnMut/FnOnce describe how the closure uses what it captured, not how it captured it.
What the Compiler Says: A Live Closure Still Holds Its Borrow
Section titled “What the Compiler Says: A Live Closure Still Holds Its Borrow”A FnMut closure holds &mut captured_var for as long as the closure itself is going to be used again — not just for the duration of one call. Reading the variable directly in between two calls is a borrow conflict, the same E0502 seen in the iterators lesson, just triggered by a closure’s capture instead of an explicit &mut:
// @expect-error E0502fn main() { let mut count = 0; let mut incrementer = || { count += 1; count }; println!("first: {}", incrementer()); println!("count directly: {}", count); // ERROR: incrementer is used again below, so its &mut count borrow is still live println!("second: {}", incrementer());}error[E0502]: cannot borrow `count` as immutable because it is also borrowed as mutable --> src/main.rs:5:36 |3 | let mut incrementer = || { count += 1; count }; | -- ----- first borrow occurs due to use of `count` in closure | | | mutable borrow occurs here4 | println!("first: {}", incrementer());5 | println!("count directly: {}", count); // ERROR: incrementer is used again below, so its &mut count borrow is still live | ^^^^^ immutable borrow occurs here6 | println!("second: {}", incrementer()); | ----------- mutable borrow later used hereThe same shape also happens between two separate closures capturing the same variable mutably — two live &mut count borrows at once is E0499, not E0502, but the underlying rule (exclusive access) is identical:
// @expect-error E0499fn main() { let mut count = 0; let mut add_one = || count += 1; let mut add_two = || count += 2; // ERROR: two live mutable borrows of count add_one(); add_two();}error[E0499]: cannot borrow `count` as mutable more than once at a time --> src/main.rs:4:23 |3 | let mut add_one = || count += 1; | -- ----- first borrow occurs due to use of `count` in closure | | | first mutable borrow occurs here4 | let mut add_two = || count += 2; // ERROR: two live mutable borrows of count | ^^ ----- second borrow occurs due to use of `count` in closure | | | second mutable borrow occurs here5 | add_one(); | ------- first borrow later used hereReturning Closures
Section titled “Returning Closures”A function that returns a closure cannot name the closure’s concrete type (every closure gets a unique, compiler-generated type) — it has to return impl Fn(...) -> ... (one concrete-but-unnamed type, decided at compile time) or Box<dyn Fn(...) -> ...> (a trait object, when the concrete type genuinely varies, such as different branches returning different closures):
fn make_adder(base: i32) -> impl Fn(i32) -> i32 { move |x| x + base}
fn make_multiplier(factor: i32) -> Box<dyn Fn(i32) -> i32> { Box::new(move |x| x * factor)}
fn main() { let add5 = make_adder(5); println!("add5(10) = {}", add5(10));
let ops: Vec<Box<dyn Fn(i32) -> i32>> = vec![ make_multiplier(2), make_multiplier(3), Box::new(|x| x - 1), ]; for op in &ops { println!("op(10) = {}", op(10)); }}add5(10) = 15op(10) = 20op(10) = 30op(10) = 9make_adder always returns the same closure shape (one move |x| x + base), so impl Fn(i32) -> i32 — zero-cost, no heap allocation, no vtable — is enough. The Vec<Box<dyn Fn(i32) -> i32>> in main needs Box<dyn Fn> because it stores different concrete closure types side by side in one collection — impl Trait cannot do that, since it names exactly one concrete type per function signature.
Closures as Iterator Adaptors
Section titled “Closures as Iterator Adaptors”Iterator adaptors like map and filter accept closures directly:
let threshold = 3;let big: Vec<i32> = vec![1, 2, 3, 4, 5] .iter() .filter(|&&x| x > threshold) // closure captures threshold by & .map(|&x| x * 10) .collect();// [40, 50]fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x)}
fn apply_mut<F: FnMut() -> i32>(mut f: F) -> i32 { f() + f()}
fn apply_once<F: FnOnce() -> String>(f: F) -> String { f()}
fn main() { // Fn: borrows environment immutably let multiplier = 3; let triple = |x| x * multiplier; println!("apply triple to 5: {}", apply(triple, 5));
// FnMut: captures and mutates let mut count = 0; let mut increment = || { count += 1; count }; let result = apply_mut(&mut increment); drop(increment); // release mutable borrow before reading count println!("FnMut result: {}, count is now: {}", result, count);
// FnOnce + move: takes ownership let name = String::from("Rust"); let greeting = move || format!("Hello, {}!", name); println!("{}", apply_once(greeting));
// closures as iterator adaptors let nums = vec![1, 2, 3, 4, 5]; let threshold = 3; let big: Vec<i32> = nums .iter() .filter(|&&x| x > threshold) .map(|&x| x * 10) .collect(); println!("big: {:?}", big);}Compiling…
Mental Model
Section titled “Mental Model”A closure is sugar for an anonymous struct plus a trait impl: the compiler generates a struct holding exactly the captured variables (by &, &mut, or value — whichever the body needs), and implements Fn/FnMut/FnOnce for it based on what the call operation does with those fields. Fn/FnMut/FnOnce form a hierarchy, not three unrelated options: every Fn closure is also FnMut and FnOnce (you can always call something fewer times or through a weaker borrow than it needs), but not the reverse. When you see E0525, read it as “this closure’s generated struct only implements the weaker trait, but the call site demanded the stronger one.”
Trade-offs
Section titled “Trade-offs”impl Fn(...) -> ... as a return type costs nothing at runtime (the compiler monomorphizes the concrete closure type into the caller) but requires every code path in the function to return the same concrete closure type. Box<dyn Fn(...) -> ...> costs one heap allocation and one vtable indirection per call, but lets you return (or store in one Vec) genuinely different closures. Borrowing by default (Fn/FnMut) keeps captured data usable outside the closure; move gives up that flexibility in exchange for a closure that can outlive its defining scope.
Common Misconceptions
Section titled “Common Misconceptions”- “
movemakes a closureFnOnce.”moveonly changes how variables are captured (by value instead of by reference); it says nothing about how the closure’s body then uses them. Amoveclosure that only reads its captured data is stillFn— theE0525example above needed an explicitdrop(name)inside the body to becomeFnOnce, not justmove. - “
Fn,FnMut, andFnOnceare three separate, mutually exclusive categories a closure belongs to.” They form a hierarchy: every closure implementsFnOnce; a closure that doesn’t consume its captures also implementsFnMut; one that doesn’t even mutate them also implementsFn. A closure that “isFn” implements all three at once. - “Since
increment()was already called once, its borrow ofcountshould be over by the time I readcountdirectly.” Non-lexical lifetimes end a borrow at its last actual use, not at its first — if the closure is called again later in the same function, the borrow spans the whole range, including any attempt to readcountdirectly in between (that’s exactly theE0502example above).
Reading AI-generated Code
Section titled “Reading AI-generated Code”AI-generated code sometimes reaches for .clone() on data captured by a closure passed to map/filter, purely to sidestep a borrow-checker complaint it didn’t fully diagnose — even when the closure only needs to read the data by reference.
🐛 Spot the bug
fn tag_all(prefix: String, items: &[String]) -> Vec<String> { items .iter() .map(|item| format!("{}{}", prefix.clone(), item)) .collect()}Show the answer
prefix.clone() runs once per element of items, cloning the same String over and over inside the closure, even though the closure only ever reads prefix — it never needs to own a fresh copy. format!("{}{}", prefix, item) borrows prefix immutably instead (closures capture by shared reference by default when the body only reads), and the closure can be called any number of times without ever cloning. The .clone() here isn’t fixing a real borrow-checker error — it is a reflex applied without checking whether ownership was actually required.