Intermediate · Closures, iterators and smart pointers · topic 31 of 50 · 30 min
Closures
Explanation
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.
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)]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.
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);Three ways to capture
Rust picks the gentlest capture that works, following the ownership rules you already know:
| The closure... | Captures by | Implements |
|---|---|---|
| only reads the variable | shared reference (&) | Fn (can be called many times, even at once) |
| changes the variable | mutable reference (&mut) | FnMut (many times, but not at once) |
| moves the variable out (gives it away) | value (ownership) | FnOnce (only once) |
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 onceEvery 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.
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
}Taking closures as parameters
fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
f(f(x))
}
println!("{}", apply_twice(|n| n + 10, 1)); // 21Common 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.
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
}Practice (0/3 done)
What does this print? let base = 10; let add = |x: i32| x + base; println!("{}", add(5) + add(1));
Why does this not compile? let mut total = 0; let add = |x: i32| total += x; add(5);
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- IteratorsClosures, iterators and smart pointers · same module
- Box<T>: values on the heapClosures, iterators and smart pointers · same module
- Rc, RefCell and WeakClosures, iterators and smart pointers · same module
- Variadic functions, function values and closuresGo Programming (backend and cloud) · Control flow and functions
- Scope and closuresJavaScript Programming (language in depth) · Functions and closures
- Scope, hoisting and closuresFrontend Web Development · JavaScript in depth
Questions people ask about this
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.
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.
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
- 3 more topics in “Closures, iterators and smart pointers” before its exam: Iterators, Box<T>: values on the heap, Rc, RefCell and Weak.
- Module 8 exam: Closures, iterators and smart pointers · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Concurrency and async. 50 topics left in the course before the final exam.