C.W.K.
Stream
Lesson 04 of 06 · published

Closures — Functions That Capture

~11 min · collections, closures, fn-traits, move

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

A closure is an anonymous function that can capture variables from the scope where it's defined. They're the glue of iterator chains and the reason functional-style Rust reads so cleanly.

The syntax

A closure is |args| body: |x| x + 1 takes x and returns x + 1. Unlike a function, it can use variables from its surrounding scope without taking them as parameters — it captures them. The compiler infers the argument and return types from how you use it.

Three ways to capture

A closure captures each variable in the least-restrictive way it can: by immutable reference if it only reads, by mutable reference if it modifies, or by value if it must own. These three modes correspond to three traits — Fn (reads captures), FnMut (mutates captures), FnOnce (consumes captures, callable once). You rarely name them; the compiler picks, and functions that take closures declare which they accept.

The Fn traits are the ownership rules, applied to captured variables. Read-only capture is a shared borrow (Fn); mutating capture is a mutable borrow (FnMut); consuming capture is a move (FnOnce). The same aliasing logic from the Borrowing track decides which trait a closure implements.

move closures

Prefix a closure with move to force it to take ownership of everything it captures: move |x| x + base. This is essential when the closure outlives the current scope — passing it to a thread, returning it, storing it. The Concurrency track relies on move closures to hand data safely to threads.

Code

Fn, FnMut, and a move closure·rust
fn main() {
    let base = 10;
    let add_base = |x: i32| x + base; // captures `base` by reference (read only)
    println!("{}", add_base(5));      // 15

    let mut count = 0;
    let mut tick = || { count += 1; }; // FnMut: mutably captures `count`
    tick();
    tick();
    println!("{count}");              // 2

    // move: the closure takes ownership of what it captures
    let owned = String::from("hi");
    let consume = move || println!("{owned}"); // owns `owned` now
    consume();
}

External links

Exercise

Write a closure that captures a multiplier from its scope and multiplies its argument by it. Then write a move closure that captures a String and prints it, and confirm the original String is no longer usable afterward. Why does move make the second closure FnOnce-ish in spirit?
Hint
After move, the closure owns the String, so the outer scope can't use it. The capture-by-value is the same move semantics from the Ownership track — move just applies it to a closure's environment.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.