Closures in Rust: Fn, FnMut, FnOnce & Iterators

Max WellsMax WellsFounder of Rustify

TL;DR: A closure in Rust is an anonymous function that can capture variables from the surrounding scope. They are defined with |params| body syntax. Unlike regular functions, closures can borrow or take ownership of variables from their environment. The compiler automatically infers which capture mode is needed (&T, &mut T, or T) based on how the closure uses those variables. Closures implement one of three traits: Fn, FnMut, or FnOnce, determining how many times and in what way they can be called.


What Is a Closure in Rust?

A closure is an anonymous function defined inline that can capture variables from the enclosing scope, combining the flexibility of a function with access to local state.

fn main() {
    let x = 5;
 
    // Closure that captures x by reference
    let add_x = |n| n + x;
 
    println!("{}", add_x(10)); // 15
    println!("{}", add_x(20)); // 25
    println!("x is still: {}", x); // x is still accessible
}

Closures are most commonly used with iterators (.map, .filter, .fold), thread spawning, and as callbacks. They are a zero-cost abstraction, the compiler monomorphizes each closure into a unique type with no runtime overhead.


How Do Closures Capture Variables?

Rust closures capture variables in the minimum way required: by immutable reference by default, by mutable reference if they mutate, and by value if forced with move or if the variable is needed after the closure's scope.

fn main() {
    let name = String::from("Alice");
    let count = 0;
 
    // Captures `name` by &String (immutable reference)
    let greet = || println!("Hello, {name}");
    greet();
    greet(); // can call multiple times; borrows each time
 
    let mut counter = 0;
    // Captures `counter` by &mut i32 (mutable reference)
    let mut increment = || {
        counter += 1;
        counter
    };
    increment();
    increment();
 
    // `move`; takes ownership of `name`
    let greeting = move || format!("Hi, {name}!");
    // println!("{name}"); // COMPILE ERROR: name was moved into closure
}

What Are Fn, FnMut, and FnOnce?

These three traits describe how a closure interacts with its captured values ; and therefore how it can be called.

TraitCalledCapturesExample
FnOnceOnceTakes ownershipClosures that consume captured values
FnMutMultiple timesMutably borrowsClosures that mutate captured values
FnMultiple timesImmutably borrowsRead-only closures

Every closure implements at least FnOnce. If it doesn't consume its captures, it also implements FnMut. If it doesn't mutate them, it also implements Fn. The hierarchy is Fn ⊆ FnMut ⊆ FnOnce.

fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(f(x)) // called twice; needs Fn, not just FnOnce
}
 
fn apply_once<F: FnOnce(String) -> String>(f: F, s: String) -> String {
    f(s) // called once; FnOnce is enough
}
 
fn main() {
    let double = |x| x * 2;
    println!("{}", apply_twice(double, 3)); // 12
 
    let prefix = String::from("Hello, ");
    let greet = move |name: String| prefix + &name; // consumes prefix
    println!("{}", apply_once(greet, "world".to_string()));
}

How Are Closures Used With Iterators?

Closures are the core of Rust's iterator API, .map(), .filter(), .fold(), and others all take closures, enabling expressive data transformation chains.

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];
 
    let result: Vec<i32> = numbers
        .iter()
        .filter(|&&x| x % 2 == 0)   // keep even numbers
        .map(|&x| x * x)             // square them
        .collect();
 
    println!("{:?}", result); // [4, 16, 36]
 
    // fold; reduce to single value
    let sum: i32 = numbers.iter().fold(0, |acc, &x| acc + x);
    println!("Sum: {sum}"); // 21
}

Iterator chains with closures compile to the same machine code as hand-written loops, the closures are inlined and the chain is fused by the optimizer.


Frequently Asked Questions

A function pointer (fn(i32) -> i32) points to a named function and captures nothing. A closure is an anonymous function that can capture its environment. Closures implement Fn/FnMut/FnOnce; function pointers implement all three but only for functions with no captures. If you need to store a closure that may or may not capture state, use Box<dyn Fn(...)>.

Use a generic parameter bounded by the trait, or box it:

struct Callback<F: Fn(i32)> { handler: F }       // generic; zero-cost
struct DynCallback { handler: Box<dyn Fn(i32)> }  // dynamic; heap alloc

The generic approach is preferred when the closure type is known at compile time.

std::thread::spawn requires 'static, the closure must own all its data because the thread may outlive the current scope. Add move to transfer ownership of captured variables into the closure.

Yes: async move || { ... } creates an async closure. This feature is stabilized in Rust 1.85+ via the async_closure feature. Before that, the common pattern was move || async move { ... }.


Sources


  • Trait: Fn, FnMut, and FnOnce are traits
  • Ownership: Closures obey the same move/borrow rules as all Rust values
  • Iterator: Closures are the primary way to use iterators
  • Lifetime: Closures that borrow from the environment have implicit lifetimes

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?