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.
Basic closure syntax
Section titled “Basic closure syntax”fn main() { let add_one = |x: i32| x + 1; println!("{}", add_one(5));}Output:
6The 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 functionlet add_one_closure = |x: i32| x + 1; // the same thing as a closureRust 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().
Multi-line closures
Section titled “Multi-line closures”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 oddformat! 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:
9040discount 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.
Closures that change captured variables
Section titled “Closures that change captured variables”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 1count is now 2count is now 3final count: 3If 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.
Where closures show up in practice
Section titled “Where closures show up in practice”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]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.
Practice
Section titled “Practice”1. Prices with tax
Section titled “1. Prices with tax”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 60120 with tax is 144tax_percent is never passed in. The closure captures it from main, which is exactly what a regular fn couldn’t do.
2. An ID generator
Section titled “2. An ID generator”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 1second user gets id 2third user gets id 3Both 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
mutvariable, 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
Section titled “Quick check”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
Read more
Section titled “Read more”- Closures — The Rust Book: the official chapter on closures, type inference, and the
Fntraits. - Capturing References or Moving Ownership — The Rust Book: the three ways a closure can capture a variable, and when to use
move. - Closures — Rust by Example: short runnable examples comparing closures with functions.
- Capturing — Rust by Example: more examples of closures that read, change, and take ownership of variables.
- Closure expressions — The Rust Reference: the exact rules for closure syntax and capture.