Site

Closures — Rust Anonymous Functions

Tutorial S7.0  •  Rust / Learn / StdLib

S7.0 What This Teaches

A closure is an anonymous function that can capture variables from its enclosing scope. Closures are the backbone of Rust's iterator adapters, threading, and callbacks. This tutorial covers:

S7.1 Basic Closure Syntax

Parameters are between |...|. The body is a single expression (or a { } block for multiple statements). Rust infers parameter and return types from usage - explicit annotations are rarely needed:
let double = |x: i32| x * 2;
println!("{}", double(5));   // 10

let clamp = |x: i32, lo: i32, hi: i32| {
    if x < lo { lo } else if x > hi { hi } else { x }
};
println!("{}", clamp(15, 0, 10));   // 10

S7.2 Capturing Variables

Closures automatically capture variables from the surrounding scope. The capture mode depends on how the variable is used: By shared reference - the closure reads but does not mutate:
let threshold = 5;
let above = |x: i32| x > threshold;   // borrows threshold
println!("{}", above(7));              // true
By mutable reference - the closure mutates a variable in scope:
let mut count = 0;
let mut increment = || { count += 1; count };
println!("{}", increment());  // 1
println!("{}", increment());  // 2
By value with move - transfers ownership into the closure. Required when the closure must outlive the scope where the variable was declared (and always required for thread closures):
let greeting = String::from("hello");
let consume = move || greeting.to_uppercase();
println!("{}", consume());   // HELLO
// greeting is no longer usable here

S7.3 The Three Closure Traits

Rust categorizes closures by how they use their captured environment:
TraitCallableWhat it can do with captures
FnOnceOnceMay move out of captured values
FnMutMany timesMay mutate captured values
FnMany timesOnly reads captured values
Every closure implements at least FnOnce. Closures that do not move out of captures also implement FnMut. Closures that do not mutate captures also implement Fn. When writing a function that accepts a closure, choose the least restrictive bound that satisfies your needs:
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32      { f(x) }
fn apply_mut<F: FnMut() -> i32>(mut f: F) -> i32       { f() }
fn apply_once<F: FnOnce() -> String>(f: F) -> String   { f() }

S7.4 Returning a Closure

Functions can return closures. Use impl Fn(...) as the return type. The move keyword is usually needed to move captured values into the returned closure:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n   // n is moved into the closure
}

let add5  = make_adder(5);
let add10 = make_adder(10);
println!("{}", add5(3));    // 8
println!("{}", add10(3));   // 13
Each call to make_adder produces a distinct closure that owns its own copy of n.

S7.5 Closures with Iterators

Closures and iterators are designed to work together. The iterator adapters map, filter, fold, and others all take closures:
let nums = vec![1, 2, 3, 4, 5];
let sum: i32 = nums.iter()
    .filter(|&&x| x % 2 != 0)   // odd numbers
    .map(|&x| x * x)             // squared
    .sum();
println!("{sum}");   // 35

S7.6 Example - All Together

// Closures - demonstrates closure syntax, capture modes, and Fn/FnMut/FnOnce bounds.

fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32      { f(x) }
fn apply_mut<F: FnMut() -> i32>(mut f: F) -> i32       { f() }
fn apply_once<F: FnOnce() -> String>(f: F) -> String   { f() }

fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

fn main() {
    // --- basic closure syntax ---
    println!("--- basic ---");
    let double = |x: i32| x * 2;
    println!("double(5) = {}", double(5));

    let clamp = |x: i32, lo: i32, hi: i32| {
        if x < lo { lo } else if x > hi { hi } else { x }
    };
    println!("clamp(15, 0, 10) = {}", clamp(15, 0, 10));

    // --- Fn: capture by shared reference ---
    println!("--- Fn (shared ref) ---");
    let threshold = 5;
    let above = |x: i32| x > threshold;
    println!("above(3): {}, above(7): {}", above(3), above(7));

    // --- FnMut: capture by mutable reference ---
    println!("--- FnMut (mut ref) ---");
    let mut count = 0;
    let mut increment = || { count += 1; count };
    println!("{}", increment());
    println!("{}", increment());
    println!("{}", increment());

    // --- Fn trait bound ---
    println!("--- Fn bound ---");
    println!("apply square: {}", apply(|x| x * x, 7));

    // --- FnMut trait bound ---
    println!("--- FnMut bound ---");
    let mut total = 0;
    println!("apply_mut: {}", apply_mut(|| { total += 10; total }));

    // --- FnOnce: moves a captured value, callable only once ---
    println!("--- FnOnce ---");
    let greeting = String::from("hello");
    let consume = move || greeting.to_uppercase();
    println!("once: {}", apply_once(consume));

    // --- returning a closure ---
    println!("--- returning closure ---");
    let add5  = make_adder(5);
    let add10 = make_adder(10);
    println!("add5(3)={}, add10(3)={}", add5(3), add10(3));

    // --- closures with iterators ---
    println!("--- closure + iterator ---");
    let nums = vec![1, 2, 3, 4, 5];
    let sum: i32 = nums.iter()
        .filter(|&&x| x % 2 != 0)
        .map(|&x| x * x)
        .sum();
    println!("sum of odd squares: {sum}");
}
Expected output:
--- basic ---
double(5) = 10
clamp(15, 0, 10) = 10
--- Fn (shared ref) ---
above(3): false, above(7): true
--- FnMut (mut ref) ---
1
2
3
--- Fn bound ---
apply square: 49
--- FnMut bound ---
apply_mut: 10
--- FnOnce ---
once: HELLO
--- returning closure ---
add5(3)=8, add10(3)=13
--- closure + iterator ---
sum of odd squares: 35

S7.7 Exercise

Exercise
  • Write a function apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 that applies f to x and then applies f again to the result. Call it with a doubling closure and print the result for input 3.
  • Write a function make_multiplier(factor: i32) -> impl Fn(i32) -> i32 that returns a closure multiplying its input by factor. Create multipliers for 3 and 7 and apply each to several values.
  • Use fold with a closure to compute the product of all even numbers in vec![1,2,3,4,5,6]. The result should be 48.

S7.8 Common Mistakes

Using a moved value after a move closure

let s = String::from("hi");
let f = move || println!("{s}");
println!("{s}");   // error: s was moved into f
After move, s belongs to the closure. Either clone it before the closure or restructure so you do not need s afterward.

Calling an FnOnce closure more than once

let s = String::from("hi");
let consume = move || drop(s);
consume();
consume();   // error: cannot call FnOnce closure more than once
A closure that moves out of a capture can only run once. Use Fn or FnMut closures when repeated calls are needed.

Capturing a mutable reference in two closures at once

let mut count = 0;
let inc  = || count += 1;
let read = || println!("{count}");   // error: count already borrowed mutably by inc
Rust does not allow a mutable and any other borrow of the same variable to coexist. Drop inc before creating read, or restructure to avoid the overlap.

S7.9 Key Terms

TermMeaning
closureAn anonymous function that captures variables from its enclosing scope
|x|Closure parameter syntax; analogous to fn(x) but inlined
captureA variable from the outer scope used inside the closure
moveKeyword that forces the closure to take ownership of all captured values
FnClosure trait: reads captures; callable many times
FnMutClosure trait: mutates captures; callable many times
FnOnceClosure trait: may move out of captures; callable only once
impl Fn(T) -> RReturn type syntax for a closure returned from a function