5.2 KiB
Closures: Anonymous Functions that Capture Their Environment
Rust closures are anonymous functions you can save in a variable or pass as an argument ot other functions
You create the closure in one plcae and then call the closure elsewhere to evaluate it in a different context.
Unlike functions, closures can capture values form the scope in which they are defined
This will be demonstrated how thee closure features allow for code reuse and behavior customization
Capturing the Environment with Closures
We will first examine how we can use closures to capture values fomr the environment they are defined in for later use
Here is the scenario that we will use to examine this:
Every so often, our t-shirt company gives away an exclusive, limited-edition shirt to someone on our mailing list as a promotion.
People on the mailing list can optionally add their colour to their profile.
If the persion cohsen for a free shirt has their favourite colour set, they get that colour shirt.
If the persion hasn't specified a favourite colour, they get whatever colour the company currently gas the most of.
There are many ways to implement this.
For example we are going to use an enum called ShirtColor
that has the varaiants Red
and Blue
(we limit the number of colours avaialable for simplicity)
We represent the company's inventory with an Inventory
struct that has a field named shirts
that contains a Vec<ShirtColor>
reperenting the shirt colours currently in stock.
The method giveaway
defined on Inventory
gets the optional shirt colour preference of the free shirt winner, and reutnrs the shirt colour the person will get.
Here is the setup
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
Red,
Blue,
}
struct Inventory {
shirts: Vec<ShirtColor>,
}
impl Inventory {
fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
user_preference.unwrap_or_else(|| self.most_stocked())
}
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;
for color in &self.shirts {
match color {
ShirtColor::Red => num_red += 1,
ShirtColor::Blue => num_blue += 1,
}
}
if num_red > num_blue {
ShirtColor::Red
} else {
ShirtColor::Blue
}
}
}
fn main() {
let store = Inventory {
shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
};
let user_pref1 = Some(ShirtColor::Red);
let giveaway1 = store.giveaway(user_pref1);
println!(
"The user with preference {:?} gets {:?}",
user_pref1, giveaway1
);
let user_pref2 = None;
let giveaway2 = store.giveaway(user_pref2);
println!(
"The user with preference {:?} gets {:?}",
user_pref2, giveaway2
);
}
The store
defined in main
has two blue shirts and one red shirt remaining to distribute for this limited-edition promotion
We call the giveaway
method for a user with a preference for a red shirt and a user without any preference.
Once again this code could b implemented in man ways and here, to focus on closures, we are stuck to concepts you already learned except for the body of the giveaway
method that uses a closure.
In the giveaway
method, we get the user preference as a paramter of type Option<ShirtColor>
and call the unwrap_or_else
method on user_preference
The unwrap_or_else
method on Option<T>
is defined by the std library.
It takes one argument: a closure without any args that returns a value T
(the same tpe stored in the Some
variant of the Option<T>
, in this case ShirtColor
)
If the Option<T>
is the Some
variant unwrap_or_else
returns the value form within the Some
.
If the Option<T>
is the None
varaint, unwrap_or_else
calls the closure and returns the value returned by the cloosure.
We specify the closure expression || self.most_stocked()
as the argument to unwrap_or_else
.
This is a closure that takes no paramters itself (if the closure has paramters, they would appear between the two verticalbars).
The body of thee closure calls self.most_stocked()
We are defining the closure here and the implementation of unwrap_or_else
will evaluate the closure later if the result is needed
Here is the output of this code
$ cargo run
Compiling shirt-company v0.1.0 (file:///projects/shirt-company)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/shirt-company`
The user with preference Some(Red) gets Red
The user with preference None gets Blue
One aspect to notice is here is that we have passed a closure that calls self.most_stocked()
on current Inventory
intance.
The std library didn't need to know anything about te Inventory
or ShirtColor
types we defined or the logic we want to use in this scenario.
The closure captures an immutalbe reference to the self
Inventory
instance and passes ot woth the code we specify to the unwrap_or_else
method
Funcions on the other hand are not able to capture their environment in this way.