Skip to content
Closures: Fn, FnMut, FnOnce
step 1/6

Reading — step 1 of 6

Learn

~3 min readSmart Pointers and Memory

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. Implies FnOnce.
  • Fn — can be called many times without mutation. Implies FnMut and FnOnce.

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 FnOnce closure twice — compile error.
  • Forgetting move when spawning threads — closure can't outlive the captured borrows.
  • Returning a non-'static closure — compile error if it captures non-'static borrows.
  • Mutating a captured binding without &mut — closure must be marked FnMut.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…