SyntaxStudy
Sign Up
Rust Closures: Syntax and Capturing
Rust Beginner 1 min read

Closures: Syntax and Capturing

Closures in Rust are anonymous functions that can capture variables from their surrounding environment. The syntax is `|params| expression` or `|params| { block }`. Unlike free functions, closures can close over variables in the enclosing scope by borrowing them immutably, borrowing them mutably, or consuming them (moving them into the closure). The compiler infers which capturing mode is needed based on how the closure uses the variables. The `move` keyword forces a closure to take ownership of all captured variables, even if borrowing would suffice. This is necessary when the closure outlives the scope where the variables were defined — for example, when spawning a thread or returning a closure from a function. Without `move`, the closure holds references, and the borrow checker would reject the code if the referenced data could be dropped before the closure is called. Closures are a fundamental tool for Rust's functional-style iterator pipelines. Methods like `map`, `filter`, `fold`, and `sort_by` all accept closures. Because the closure type is inferred and monomorphised at compile time when used with generics, there is no overhead compared to a hand-written loop. The compiler can even inline the closure body into the surrounding code.
Example
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}

fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
    move |x| x * factor   // 'factor' is moved into the closure
}

fn main() {
    // Basic closure syntax
    let double = |x: i32| x * 2;
    let add_three = |x| x + 3;      // types inferred from use

    println!("double(5) = {}", double(5));
    println!("add_three(5) = {}", add_three(5));

    // Closure capturing environment by immutable borrow
    let offset = 10;
    let add_offset = |x| x + offset;
    println!("add_offset(5) = {}  offset still = {offset}", add_offset(5));

    // Closure capturing by mutable borrow
    let mut total = 0;
    let mut accumulate = |x: i32| { total += x; };
    accumulate(1); accumulate(2); accumulate(3);
    drop(accumulate);   // release borrow so we can read total again
    println!("total = {total}");

    // move closure
    let msg = String::from("hello from closure");
    let greet = move || println!("{msg}");
    greet();
    // println!("{msg}"); // compile error: moved into closure

    // Higher-order function
    println!("apply double: {}", apply(double, 7));
    println!("apply |x| x-1: {}", apply(|x| x - 1, 7));

    // Returning a closure (factory)
    let times7 = make_multiplier(7);
    println!("times7(6) = {}", times7(6));
}