Skip to content

Rust Closures

A closure is a small, unnamed function you can store in a variable and pass around. The thing that makes them different from regular functions is that they can “capture” variables from the scope they were created in.

fn main() {
let add_one = |x: i32| x + 1;
println!("{}", add_one(5));
}

Output:

6
▶ Try it Yourself

The pipes |x: i32| stand in for the parentheses a regular function would use. Compare the two side by side:

fn add_one_fn(x: i32) -> i32 { x + 1 } // a regular function
let add_one_closure = |x: i32| x + 1; // the same thing as a closure

Rust can often infer the parameter and return types too, so you’ll frequently see closures written even shorter:

let add_one = |x| x + 1;

A closure with no parameters uses empty pipes: let say_hi = || println!("hi");, and you call it with say_hi().

For more than one expression, wrap the body in braces just like a function:

fn main() {
let describe = |n: i32| {
let kind = if n % 2 == 0 { "even" } else { "odd" };
format!("{n} is {kind}")
};
println!("{}", describe(7));
}

Output:

7 is odd
▶ Try it Yourself

format! works like println!, but instead of printing the text it gives it back as a String. Here, that String is the last expression in the braces, so it’s what the closure returns.

Capturing variables from the surrounding scope

Section titled “Capturing variables from the surrounding scope”

Here’s the feature a regular function doesn’t have: a closure can use variables from wherever it was defined, without you passing them in explicitly:

fn main() {
let discount = 10;
let apply_discount = |price: i32| price - discount;
println!("{}", apply_discount(100)); // 90
println!("{}", apply_discount(50)); // 40
}

Output:

90
40
▶ Try it Yourself

discount isn’t a parameter of the closure — it’s just sitting in main’s scope, and the closure reaches out and uses it directly. A regular fn can’t do this; it would need discount passed in as an argument.

A closure can also change a variable it captures, as long as that variable is mut. There’s one extra rule: the closure itself must be stored in a mut variable too. Calling it changes something, and Rust wants that to be visible in your code.

fn main() {
let mut count = 0;
let mut increment = || {
count += 1;
println!("count is now {count}");
};
increment();
increment();
increment();
println!("final count: {count}");
}

Output:

count is now 1
count is now 2
count is now 3
final count: 3
▶ Try it Yourself

If you forget the mut on increment, the compiler tells you exactly what’s wrong: calling increment requires mutable binding due to mutable borrow of count.

There’s a catch worth knowing. While the closure can still be called, it has the right to change count, so nobody else may touch count. If you add println!("{count}"); between two increment() calls, you get error[E0502]: cannot borrow count as immutable because it is also borrowed as mutable. After the last call to increment(), the closure is done, and count is all yours again. That’s why the final count line works. You’ll learn the full story behind this rule on the Borrowing page.

Sometimes a closure needs to take full ownership of what it captures, for example when it’s handed to another thread. You write move before the pipes for that: move || { ... }. You’ll see it in action on the Concurrency page.

You won’t reach for closures much on their own — where they’re genuinely useful is passing custom behavior into another function, especially the iterator methods you’ll meet on the Iterators page:

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);
}

Output:

[2, 4, 6, 8, 10]
▶ Try it Yourself

Here, |n| n * 2 is a closure passed straight into .map() — this pattern (a small closure driving a method like .map() or .filter()) is where you’ll actually use them day to day.

Make a variable tax_percent set to 20. Then write a closure with_tax that takes a price and returns the price plus tax, using the captured tax_percent. Print the result for prices 50 and 120. Hint: the tax is price * tax_percent / 100.

Show solution
fn main() {
let tax_percent = 20;
let with_tax = |price: i32| price + price * tax_percent / 100;
println!("50 with tax is {}", with_tax(50));
println!("120 with tax is {}", with_tax(120));
}

Output:

50 with tax is 60
120 with tax is 144
▶ Try it Yourself

tax_percent is never passed in. The closure captures it from main, which is exactly what a regular fn couldn’t do.

Write a closure next_id that returns 1 the first time you call it, 2 the second time, 3 the third time, and so on. Call it three times and print each ID. Hint: keep a mut counter outside the closure, add 1 to it inside, and make the counter the closure’s last expression so it’s returned.

Show solution
fn main() {
let mut last_id = 0;
let mut next_id = || {
last_id += 1;
last_id
};
println!("first user gets id {}", next_id());
println!("second user gets id {}", next_id());
println!("third user gets id {}", next_id());
}

Output:

first user gets id 1
second user gets id 2
third user gets id 3
▶ Try it Yourself

Both last_id and next_id need mut: the first because its value changes, the second because calling the closure changes what it captured.

  • A closure is a small anonymous function: |params| expression.
  • Types are often inferred, so closures are usually shorter than an equivalent fn.
  • Unlike a regular function, a closure can capture variables from the scope it was defined in.
  • A closure that changes a captured variable must be stored in a mut variable, and it borrows that variable for as long as it’s used.
  • Closures are most useful passed into other functions — especially iterator methods like .map().

Quick check

1. What symbols does a closure use instead of a function's parentheses for parameters?

2. What can a closure do that a regular fn cannot?

3. Where does this page say closures are most commonly used in practice?

4. A closure does count += 1 on a captured variable. What else do you need?

Score: 0 / 4