Rust Closures
A closure is a small function without a name that you can write right where you need it, store in a variable, or hand to another function. If a normal function is a recipe written in a cookbook, a closure is a quick note on a sticky label: short, handy, and used on the spot.
Closures use vertical bars | | for their parameters, followed by the body:
fn main() {
let double = |x: i32| x * 2;
let greet = |name: &str| format!("Hello, {}!", name);
println!("{}", double(21));
println!("{}", greet("Neha"));
}42 Hello, Neha!
For a closure with a longer body, use curly brackets, just like a function: |x| { let y = x + 1; y * y }. Rust can usually work out the types of the parameters, so you often leave them out.
Closures remember their surroundings
Here is the special thing about closures: they can use variables from the place where they were created. This is called capturing the environment. A normal fn cannot do this.
fn main() {
let discount = 15;
let final_price = |price: u32| price - price * discount / 100;
println!("400 becomes {}", final_price(400));
println!("80 becomes {}", final_price(80));
}400 becomes 340 80 becomes 68
The closure "remembers" discount. If you need a closure to change a captured variable, the variable must be mut, and the closure itself must be stored in a mut variable too:
fn main() {
let mut clicks = 0;
let mut click = || clicks += 1;
click();
click();
click();
println!("Clicked {} times", clicks);
}Clicked 3 times
Passing closures to methods
The most common use of closures is to tell a method how to do its job. Sorting by a key, keeping some items, finding the first match: each takes a closure.
fn main() {
let mut words = vec!["banana", "fig", "cherry", "kiwi"];
words.sort_by_key(|w| w.len());
println!("Shortest first: {:?}", words);
let long: Vec<_> = words.iter().filter(|w| w.len() > 4).collect();
println!("Long words: {:?}", long);
let starts_with_c = words.iter().find(|w| w.starts_with('c'));
println!("First with c: {:?}", starts_with_c);
}Shortest first: ["fig", "kiwi", "banana", "cherry"]
Long words: ["banana", "cherry"]
First with c: Some("cherry")sort_by_key keeps items with the same key in their original order, which is why "banana" stays before "cherry".
Functions that take closures
You can write your own functions that accept a closure. The type impl Fn(i32) -> i32 means "anything that can be called with an i32 and gives back an i32":
fn apply_twice(f: impl Fn(i32) -> i32, value: i32) -> i32 {
f(f(value))
}
fn main() {
println!("{}", apply_twice(|x| x + 10, 1));
println!("{}", apply_twice(|x| x * x, 3));
}21 81
Practice exercises
- Write a closure
is_evenand use it to print which numbers from 1 to 10 are even. - Sort a vector of names by their last letter using
sort_by_key. - Write
fn repeat(times: u32, f: impl Fn(u32))that callsfwith 1, 2, 3 and so on.