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.96 Fn / FnMut / FnOnce Last verified:
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

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.