Skip to content

Closures

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 environment
println!("{}", triple(7)); // 21

Rust has three closure traits that describe how a closure uses its captured values:

TraitHow it capturesCan be called
FnBorrows immutably (&T)Any number of times
FnMutBorrows mutably (&mut T)Any number of times
FnOnceTakes 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]
How the compiler infers Fn / FnMut / FnOnce
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(x) }
let base = 10;
let add_base = |x| x + base; // borrows base immutably
println!("{}", apply(add_base, 5)); // 15
let mut count = 0;
let mut increment = || { count += 1; count };
println!("{}", increment()); // 1
println!("{}", increment()); // 2
drop(increment); // release the mutable borrow
println!("count is {}", count); // 2

drop(increment) must be called before reading count directly, because increment holds a &mut count that would conflict with the immutable borrow in println!.

let name = String::from("Rust");
let greet = || println!("Hello, {}!", name); // moves name into closure
greet(); // name is consumed here
// greet(); // would compile-error — FnOnce can only be called once

What 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 E0525
fn 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 environment
12 | };
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.

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 moved
say();

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 E0502
fn 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 here
4 | 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 here
6 | println!("second: {}", incrementer());
| ----------- mutable borrow later used here

The 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 E0499
fn 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 here
4 | 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 here
5 | add_one();
| ------- first borrow later used here

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) = 15
op(10) = 20
op(10) = 30
op(10) = 9

make_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.

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);
}

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.”

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.

  • “move makes a closure FnOnce.” move only changes how variables are captured (by value instead of by reference); it says nothing about how the closure’s body then uses them. A move closure that only reads its captured data is still Fn — the E0525 example above needed an explicit drop(name) inside the body to become FnOnce, not just move.
  • “Fn, FnMut, and FnOnce are three separate, mutually exclusive categories a closure belongs to.” They form a hierarchy: every closure implements FnOnce; a closure that doesn’t consume its captures also implements FnMut; one that doesn’t even mutate them also implements Fn. A closure that “is Fn” implements all three at once.
  • “Since increment() was already called once, its borrow of count should be over by the time I read count directly.” 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 read count directly in between (that’s exactly the E0502 example above).

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.

What closure trait describes a closure that mutably borrows a captured variable?
Why must drop(increment) be called before println!("{}", count) in an FnMut example?
What does the move keyword do to a closure?
A closure that only reads a captured variable without modifying it implements which trait(s)?
What does this fail to compile with?
fn call_twice<F: Fn()>(f: F) {
    f();
    f();
}
fn main() {
    let s = String::from("hi");
    let c = move || { println!("{}", s); drop(s); };
    call_twice(c);
}
Why does a function returning different concrete closures on different branches need Box<dyn Fn(...)> instead of impl Fn(...)?
In items.iter().map(|item| format!("{}{}", prefix.clone(), item)), why is prefix.clone() usually unnecessary?