Skip to content

Intermediate · Closures, iterators and smart pointers · topic 31 of 50 · 30 min

Closures

Explanation

A closure is a small function without a name, written |x| x + 1, that can use variables from the place where it is created. It captures them by reference, by mutable reference or by value (with move). Depending on how it uses what it captured, a closure implements Fn, FnMut or FnOnce, which decides where it can be passed.

Why closures?

Many functions take "a bit of behaviour" as input: sort these orders by this key, keep only items that pass this test, run this when the button is clicked. Writing a full named function for each tiny rule is noisy. A closure is an anonymous (nameless) function you write right where you need it.

rust
let add_gst = |price: u32| price + price * 18 / 100;
println!("{}", add_gst(100));          // 118

let mut orders = vec![("Asha", 450), ("Ravi", 120), ("Meena", 980)];
orders.sort_by_key(|o| o.1);             // sort by amount
println!("{:?}", orders);                // [("Ravi", 120), ("Asha", 450), ("Meena", 980)]
Parameters go between bars |...|. Types can usually be inferred.

Capturing the environment

The big difference from a normal fn: a closure can use variables from the surrounding scope. This is called capturing. A normal fn cannot see local variables of other functions.

rust
let min_order = 200;
let big_orders: Vec<_> = orders.iter()
    .filter(|o| o.1 >= min_order)        // uses min_order from outside
    .collect();
println!("{:?}", big_orders);
The closure borrows min_order. A fn inside main could not do this.

Three ways to capture

Rust picks the gentlest capture that works, following the ownership rules you already know:

The closure...Captures byImplements
only reads the variableshared reference (&)Fn (can be called many times, even at once)
changes the variablemutable reference (&mut)FnMut (many times, but not at once)
moves the variable out (gives it away)value (ownership)FnOnce (only once)
rust
let mut count = 0;
let mut tick = || count += 1;   // FnMut: changes count
tick();
tick();
println!("{count}");            // 2

let name = String::from("Kabir");
let consume = move || name;     // FnOnce: returns (moves out) name
let n = consume();
// consume();                   // error: closure can only be called once
tick must be declared mut because calling it changes captured state.

Every closure that implements Fn also counts as FnMut and FnOnce, so a function that asks for FnOnce accepts any closure. A function that asks for Fn is the strictest.

The move keyword

move forces the closure to take ownership of everything it captures, even if it only reads. You need it when the closure must outlive the current scope, such as when you send it to another thread (module 9) or return it from a function.

rust
fn make_multiplier(factor: u32) -> impl Fn(u32) -> u32 {
    move |x| x * factor      // factor is copied into the closure
}

fn main() {
    let triple = make_multiplier(3);
    println!("{}", triple(7));   // 21
}
Without move, the closure would borrow factor, which is dropped when make_multiplier returns.

Taking closures as parameters

rust
fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(f(x))
}

println!("{}", apply_twice(|n| n + 10, 1));   // 21
Use the most flexible bound you can: FnOnce if you call it once, FnMut if you call it many times and it may change state, Fn if it must not.
Think of it like this: A closure is like a sticky note with instructions that you hand to someone, with a few things clipped to it. "Add 18 percent GST" might have nothing clipped. "Keep orders above the minimum" has the minimum clipped on (borrowed). With move, you staple the original items to the note and give them away.

Common mistakes

  • Forgetting mut on a closure that changes captured variables: "cannot borrow as mutable". Write let mut tick = ...
  • Returning a closure that borrows a local variable: "may outlive borrowed value". Add move.
  • Using a variable after a move closure took it. Clone it before the closure if you need both.
  • Asking for Fn when the caller passes a closure that changes state. Use FnMut.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Closures · 1/6

let add_gst = |p: u32| p + p * 18 / 100;

A closure is a small function without a name, written with vertical bars around its parameters.

Prefer a video by a teacher? Find free videos on “rust closures fn fnmut fnonce move explained” on YouTube ↗

In real life: Filters in a property search app

A property website lets users filter flats by budget and city. The search code builds closures from the user's choices, each capturing the chosen values, and runs every listing through them. Adding a new filter (for example "near a metro station") means one more closure, not a change to the search loop.

rust
struct Flat { city: &'static str, rent: u32 }

fn main() {
    let flats = [
        Flat { city: "Pune", rent: 18000 },
        Flat { city: "Pune", rent: 32000 },
        Flat { city: "Nashik", rent: 12000 },
    ];
    let budget = 25000;
    let wanted = "Pune";
    let fits = |f: &Flat| f.rent <= budget && f.city == wanted;
    let count = flats.iter().filter(|&f| fits(f)).count();
    println!("{count} flat(s) match");   // 1 flat(s) match
}
fits captures budget and wanted by reference.

Practice (0/3 done)

Task 1 · Question

What does this print? let base = 10; let add = |x: i32| x + base; println!("{}", add(5) + add(1));

Task 2 · Question

Why does this not compile? let mut total = 0; let add = |x: i32| total += x; add(5);

Task 3 · Practice

Write fn make_discounter(percent: u32) -> impl Fn(u32) -> u32 that returns a closure taking a price. Create a 10% and a 25% discounter and apply both to a list of prices. Then write a counter closure with FnMut that counts how many times it was called.

Quick quiz

Question 1

1.What can a closure do that a normal fn cannot?

Question 2

2.Which trait does a closure implement if it can only be called once?

Question 3

3.What does move do?

Question 4

4.A function calls its closure parameter many times, and the closure may change its captured state. Which bound fits?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What can a closure do that a normal fn cannot?

Answer: Capture variables from its surrounding scope

Capturing the environment is what makes closures special.

Which trait does a closure implement if it can only be called once?

Answer: FnOnce

A closure that moves out a captured value is FnOnce.

What does move do?

Answer: Makes the closure take ownership of captured variables

move captures by value.

A function calls its closure parameter many times, and the closure may change its captured state. Which bound fits?

Answer: FnMut

FnMut allows repeated calls that mutate state.

What is a closure?Related

Answer: A function value that remembers variables from where it was created

A closure captures and keeps alive the outer variables it uses.

From Variadic functions, function values and closures

What is a closure?Related

Answer: A function plus the variables from where it was created that it remembers

Closures keep access to the variables of the scope where the function was defined.

From Scope and closures

What is a closure?Related

Answer: A function together with the outer variables it remembers

A closure is a function plus the variables from where it was created, which stay alive as long as the function does.

From Scope, hoisting and closures

What is a closure?Related

Answer: A function that remembers variables from where it was created

Closures capture their surroundings.

From Functions

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Closures, iterators and smart pointers” before its exam: Iterators, Box<T>: values on the heap, Rc, RefCell and Weak.
  2. Module 8 exam: Closures, iterators and smart pointers · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Concurrency and async. 50 topics left in the course before the final exam.

← Previous: Trait objects and dynamic dispatch