Closures & Fn Traits

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Rust 1.99.0 and the 2024 edition, checked on 2026-10-06. A closure is an anonymous function that can capture variables from the scope where it is defined. Closures are everywhere in idiomatic Rust: iterator adapters, sorting keys, callbacks, thread and task bodies, lazy defaults (unwrap_or_else). Each closure has its own unique, unnameable type, and implements one or more of the Fn, FnMut and FnOnce traits depending on what it does with its captures.

Syntax and type inference

Parameters go between pipes; the body is an expression, so braces are only needed for several statements. Parameter and return types are usually inferred from the first use:

fn main() {
    let add_tax = |cents: u32| -> u32 { cents * 121 / 100 }; // fully annotated
    let double = |x| x * 2;                                   // inferred from use below
    let greet = || println!("welcome to the shelf");          // no parameters

    println!("{} {}", add_tax(1000), double(21));
    greet();

    let mut titles = vec!["Emma", "Dune", "Beloved"];
    titles.sort_by_key(|t| t.len());                          // closure as an argument
    println!("{titles:?}");
}

A closure’s parameter types are fixed after the first call: double(21) makes double take i32, so a later double(2.5) is error E0308. When nothing pins the type, you get E0282 and need an annotation.

Capturing

A closure captures each variable it mentions in the least demanding way the body allows:

  1. by shared reference (&T) if it only reads the variable;

  2. by mutable reference (&mut T) if it modifies it;

  3. by value (move) if it moves the variable out (for example, returns it or passes it to a function taking ownership).

The usual borrowing rules apply while the closure exists: a closure that borrows count mutably blocks other uses of count until its last use.

fn main() {
    let catalogue = vec!["Dune", "Emma"];
    let count = || catalogue.len();          // captures &catalogue
    println!("{} {:?}", count(), catalogue); // reading catalogue too is fine

    let mut lent = 0;
    let mut lend = || lent += 1;             // captures &mut lent; the binding must be `mut`
    lend();
    lend();
    println!("lent {lent}");                 // the mutable borrow has ended

    let title = String::from("Kindred");
    let consume = || drop(title);            // captures title by value
    consume();
    // consume();                            // would not compile: closure moved `title` already (E0382)
}

move closures

move forces every capture to be by value, whatever the body does. It is required whenever the closure may outlive the current scope — returned from a function, sent to another thread, or spawned as an async task — because borrows of local variables would dangle. Without it, the compiler reports E0373 ("closure may outlive the current function, but it borrows x`") and suggests adding `move.

use std::thread;

fn make_counter() -> impl FnMut() -> u32 {
    let mut n = 0;
    move || {                        // n moves into the closure; it lives as long as the closure
        n += 1;
        n
    }
}

fn main() {
    let mut next_id = make_counter();
    println!("{} {}", next_id(), next_id()); // 1 2

    let titles = ["Dune".to_string(), "Emma".to_string()];
    let handle = thread::spawn(move || titles.len()); // titles moves into the thread
    println!("{}", handle.join().unwrap());

    let shared = std::sync::Arc::new(String::from("catalog"));
    let s2 = std::sync::Arc::clone(&shared);          // clone *before* the move
    thread::spawn(move || println!("{s2}")).join().unwrap();
    println!("{shared}");
}

Moving a Copy value (such as an integer) copies it, so the original stays usable. To move some variables and borrow others, create references beforehand (let r = &big; move || r.len()) — the reference itself is what gets moved.

Disjoint field capture (2021 edition)

Since the 2021 edition, closures capture paths such as book.title rather than the whole book. Two closures can then borrow different fields at the same time, and a move closure that uses one field only moves that field:

struct Book {
    title: String,
    loans: u32,
}

fn main() {
    let mut book = Book { title: "Dune".into(), loans: 0 };
    let mut record = || book.loans += 1;     // captures &mut book.loans only
    let label = &book.title;                  // still allowed: a different field
    record();
    println!("{label} lent {} times", book.loans);
}

Fn, FnMut and FnOnce

Every closure implements one or more of three traits, determined by what its body does with its captures — not by how they are captured:

Trait Call signature Implemented when the body …​

FnOnce

call_once(self) — may be called once

moves something out of its captures (all closures implement it)

FnMut

call_mut(&mut self) — may be called repeatedly, needs exclusive access

mutates captures but does not move them out

Fn

call(&self) — may be called repeatedly, even concurrently

only reads its captures (or captures nothing)

flowchart LR subgraph FnOnce["FnOnce: callable at least once"] subgraph FnMut["FnMut: callable many times, needs &mut"] subgraph Fn["Fn: callable many times through &"] R["reads captures only: || v.len()"] N["captures nothing: |x| x + 1 (also coerces to fn pointer)"] end M["mutates captures: || count += 1"] end O["moves out of captures: || drop(title)"] end

Every Fn is also FnMut, and every FnMut is also FnOnce, so a function asking for FnOnce accepts any closure. When accepting a closure, ask for the least restrictive trait that works: FnOnce if you call it once (like Option::unwrap_or_else), FnMut if you call it repeatedly without sharing (like Iterator::for_each), Fn if you need shared or concurrent calls.

fn call_twice<F: FnMut() -> u32>(mut f: F) -> u32 {
    f() + f()
}

fn call_once<F: FnOnce() -> String>(f: F) -> String {
    f()
}

fn apply_to_all<F: Fn(&str) -> usize>(items: &[&str], f: F) -> Vec<usize> {
    items.iter().map(|s| f(s)).collect()
}

fn main() {
    let mut n = 0;
    println!("{}", call_twice(|| { n += 1; n }));      // FnMut: 1 + 2 = 3

    let title = String::from("Dune");
    println!("{}", call_once(move || title));           // FnOnce: returns the captured String

    let prefix = 3;
    println!("{:?}", apply_to_all(&["Dune", "Middlemarch"], |s| s.len() + prefix)); // Fn
}

Passing a closure that does not implement the required trait is error E0525 ("expected a closure that implements the Fn trait, but this closure only implements `FnOnce`"), and the message points to the capture that is moved or mutated.

Closures as parameters and return values

Because each closure has a unique type, functions take them through generics or impl Trait, or through a trait object when different closures must share one type (stored in a collection or a struct field):

type Rule = Box<dyn Fn(&str) -> bool>;

fn starts_with(prefix: &'static str) -> impl Fn(&str) -> bool {
    move |s| s.starts_with(prefix)
}

fn make_rules(min_len: usize) -> Vec<Rule> {
    vec![
        Box::new(move |s: &str| s.len() >= min_len),
        Box::new(starts_with("978")),
        Box::new(|s: &str| s.bytes().all(|b| b.is_ascii_digit())),
    ]
}

fn main() {
    let rules = make_rules(13);
    for isbn in ["9780441013593", "978-0441", "1234567890123"] {
        let ok = rules.iter().all(|rule| rule(isbn));
        println!("{isbn}: {ok}");
    }
}

impl Fn(...) is static dispatch with no allocation; Box<dyn Fn(...)> erases the type at the cost of a heap allocation and an indirect call. Add + Send + Sync + 'static to boxed closures that cross threads.

Function pointers

Plain functions and closures that capture nothing coerce to function pointers of type fn(Args) -> Ret. Function pointers implement all three Fn traits, so a function name can be passed wherever a closure is expected — often shorter than wrapping it in a closure:

fn main() {
    let titles = ["dune", "emma"];
    let upper: Vec<String> = titles.iter().map(|t| t.to_uppercase()).collect();
    let lengths: Vec<usize> = titles.iter().copied().map(str::len).collect(); // a method as a function
    let parsed: Vec<Option<u16>> = ["1965", "x"].iter().map(|s| s.parse().ok()).collect();
    let as_some: Vec<Option<&str>> = titles.iter().copied().map(Some).collect(); // a variant constructor
    let op: fn(u32, u32) -> u32 = |a, b| a + b; // non-capturing closure coerced to fn
    println!("{upper:?} {lengths:?} {parsed:?} {as_some:?} {}", op(2, 3));
}

Function pointers matter mainly for FFI callbacks and for tables of functions; see Functions & Expressions.

Async closures (1.85)

Rust 1.85 stabilised async closures, async |args| { ... }, and the matching traits AsyncFn, AsyncFnMut and AsyncFnOnce. Unlike a closure returning an async block (|x| async move { ... }), an async closure’s future can borrow from the closure’s captures, and the traits make "a function that takes an async callback" easy to write:

async fn retry<F>(times: u32, op: F) -> Result<String, String>
where
    F: AsyncFn(u32) -> Result<String, String>, // a bound written like Fn
{
    let mut last = Err("never ran".to_string());
    for attempt in 1..=times {
        last = op(attempt).await;
        if last.is_ok() {
            break;
        }
    }
    last
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let service = String::from("catalog");
    let result = retry(3, async |attempt| {
        // borrows `service` from the closure's captures across the await point
        tokio::task::yield_now().await;
        if attempt < 3 { Err(format!("{service} busy")) } else { Ok(format!("{service} ok on try {attempt}")) }
    })
    .await;
    println!("{result:?}");
}

AsyncFn* and Future/IntoFuture are in the 2024 prelude. As with async fn in traits, there is no syntax yet to require the returned future to be Send; for code that spawns the future on a multi-threaded runtime, the older F: Fn(T) -> Fut, Fut: Future<Output = R> + Send form is still needed. See Async/Await & Futures.

Common errors

Error Cause and fix

E0373 closure may outlive the current function

add move (and clone Arcs or other shared data beforehand)

E0525 expected Fn, found FnOnce

the closure moves a capture out; clone inside the closure, or relax the bound to FnOnce

E0596 cannot borrow as mutable

a closure that mutates its captures must be bound with let mut, and the parameter must be mut f: F or f: &mut F

E0308 mismatched types / "no two closures have the same type"

store different closures in a Vec<Box<dyn Fn(..)>>, or use function pointers

E0499/E0502 while a closure is alive

the closure holds a borrow; end its last use first, or use move