← thecodex.expert · The Codex Family of Knowledge
Rust

Closures

Rust has three separate closure traits, because the borrow checker needs to know exactly how a closure touches the variables it captured.

Rust 1.95+ Fn / FnMut / FnOnce Last verified:
Where this bites you

Writing a closure's type as the return type seems natural once you've seen |x| x + 1 work everywhere else — but Fn(i32) -> i32 is a trait, not a concrete type, and a function's return type has to be one concrete, sized thing the compiler can lay out on the stack. Every closure gets its own hidden, compiler-generated type, so the compiler can't just hand back "a closure" without saying how.

Rustreturn_bug.rs
fn make_adder(x: i32) -> Fn(i32) -> i32 {
    move |y| x + y
}

let add_five = make_adder(5);
println!("{}", add_five(10));
// error[E0746]: return type cannot be a trait object without pointer indirection
//   help: consider returning an `impl Trait` instead of a `dyn Trait`
Canonical Definition

A closure is written with pipes around its parameters (|x| x + 1) and can capture variables from its surrounding scope. The compiler infers which of three traits it implements based on how it uses those captures: FnOnce (consumes a captured value, callable once), FnMut (mutably borrows a capture, callable multiple times), or Fn (only reads captures, callable multiple times, even concurrently). The move keyword forces a closure to take ownership of its captures rather than borrowing them.

Closure syntax and type inference

Closures rarely need type annotations — the compiler infers parameter and return types from how the closure is used, unlike a function definition, which always requires them.

Rustclosures.rs
let add_one = |x: i32| x + 1;      // explicit types, optional
let multiply = |x, y| x * y;        // types inferred from first call

println!("{}", add_one(5));         // 6
println!("{}", multiply(3, 4));     // 12, infers i32 from these args

Type inference also explains the fix for the bug above: the compiler already knows the concrete closure type at every call site, so a return type only needs to describe which trait that hidden type implements, not the type itself. Writing impl Fn(i32) -> i32 instead of a bare Fn(i32) -> i32 says exactly that — "some concrete type implementing this trait" — which is sized and returnable, without naming or boxing it:

Rustreturn_fix.rs
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

let add_five = make_adder(5);
println!("{}", add_five(10));   // 15

Capturing the environment: Fn, FnMut, FnOnce

A closure that only reads a captured variable implements Fn; one that mutates a captured variable implements FnMut; one that moves a captured variable out (consuming it) implements FnOnce and can only be called once.

Rustcapture_kinds.rs
let list = vec![1, 2, 3];
let print_list = || println!("{:?}", list);   // Fn: only reads list
print_list();
print_list();   // fine, can call again — only borrowed immutably

let mut count = 0;
let mut increment = || { count += 1; };        // FnMut: mutates count
increment();
increment();
println!("{}", count);   // 2

move: taking ownership of captures

The move keyword forces a closure to take ownership of everything it captures, rather than borrowing — essential when the closure needs to outlive the current scope, such as when it's sent to a new thread.

Rustmove_closure.rs
let data = vec![1, 2, 3];

let handle = std::thread::spawn(move || {   // move: data's ownership transfers in
    println!("{:?}", data);
});
// println!("{:?}", data);   // COMPILE ERROR: data was moved into the closure

handle.join().unwrap();

Sources

1
The Rust Project. The Rust Programming Language, "Closures: Anonymous Functions that Capture Their Environment", doc.rust-lang.org/book/ch13-01.