RustBrock/Closures.md
darkicewolf50 f2e65b3550
All checks were successful
Test Gitea Actions / first (push) Successful in 13s
started ch13.1 with files
2025-02-19 23:51:24 +00:00

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.

Closure Type Inference and Annoation