An closure is an anonymous function that can capture its environment.
That is, a closure not only receives parameters. It can also remember data that was around, like someone jotting a cheat sheet on their hand (but legally and without getting kicked out of the exam).
Creating a Closure
The basic syntax uses vertical bars |...| for parameters:
fn main() {
let add_one = |x| x + 1;
println!("{}", add_one(5));
}This looks like a function, but we haven’t used fn, nor have we specified a return type. Rust infers the types from usage.
If we want, we can write the types explicitly:
fn main() {
let add = |a: i32, b: i32| -> i32 {
a + b
};
println!("{}", add(2, 3));
}Closures vs. Functions
A normal function is declared like this:
fn add_one(x: i32) -> i32 {
x + 1
}A closure is stored in a variable:
let add_one = |x| x + 1;The important difference is that a closure can capture variables from the environment.
fn main() {
let increment = 10;
let add_increment = |x| x + increment;
println!("{}", add_increment(5)); // 15
}add_increment uses increment, even though increment is not a parameter. That is the capture.
Capture by Reference
By default, Rust tries to capture in the least invasive way possible.
fn main() {
let name = String::from("Rust");
let greet = || {
println!("Hello, {}", name);
};
greet();
println!("We still have the name: {}", name);
}Here the closure only needs to read name, so it captures it by immutable reference.
Mutable Capture
If the closure modifies a variable, it needs to capture it mutably.
fn main() {
let mut counter = 0;
let mut increment = || {
counter += 1;
println!("counter = {}", counter);
};
increment();
increment();
}The closure itself must also be mut, because calling it changes its internal state.
Capture with move
Sometimes we want the closure to take the values with it. For that we use move.
fn main() {
let message = String::from("Hello from another place");
let print_message = move || {
println!("{}", message);
};
print_message();
}With move, the closure captures message by value and takes ownership of that String. This is very common when we spawn threads or asynchronous tasks, because the closure might execute later, when the original function has already finished.
move captures by value, but that doesn’t always mean invalidating the original variable. If the value implements Copy, the closure receives a copy.
Closures and Iterators
Closures particularly shine with iterators:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let evens: Vec<i32> = numbers
.into_iter()
.filter(|n| n % 2 == 0)
.map(|n| n * 10)
.collect();
println!("{:?}", evens);
}filter and map receive closures to know what to do with each element. This allows writing very compact transformations, without creating helper functions for everything.