From f921ed2326b5a861d17d56fd793108d2ac62c2e6 Mon Sep 17 00:00:00 2001 From: darkicewolf50 Date: Thu, 20 Feb 2025 23:34:59 +0000 Subject: [PATCH] finished ch13.1 --- Closures.md | 434 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 432 insertions(+), 2 deletions(-) diff --git a/Closures.md b/Closures.md index a209d68..282e618 100644 --- a/Closures.md +++ b/Closures.md @@ -87,7 +87,7 @@ The `store` defined in `main` has two blue shirts and one red shirt remaining to 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. +Once again this code could be 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` and call the `unwrap_or_else` method on `user_preference` @@ -125,4 +125,434 @@ The closure captures an immutalbe reference to the `self` `Inventory` instance a Funcions on the other hand are not able to capture their environment in this way. -## Closure Type Inference and Annoation \ No newline at end of file +## Closure Type Inference and Annoation +There are more differences between functions and closures. + +Closures don't normally require you to annotate the tpyes of the paramters or the return tpyes like `fn` functions do + +Type annotations are required on functions because the types are part of an explicit interface epxosed to your users. + +Defining this interface rigidly is important for ensuring that everyone agrees on what types of values a function uses and returns + +Closures on the other hand aren't used in an exposed interface. They are stored in variables and used without naming them and exposing them to users of our library + +Closuures are tpyically sshort and relevant only within a narrow context rather than in any arbitrary scenario. + +Within the limited contexts, the complier can infer the tpyes of the parameters and the return type of the parameters and the return tpye. + +This is similar to how it is able to infer the tpyes fo most variables (there are till cases where the compiler needs closure tpye annotations) + +With variables you can add tpye annotations if we want to increase explicitness and clarity at the cost of being more verbose than what is necessary + +Annotationg tpyes for a closure would look like this below + +In this example we are defining a closure and storing it in a varaible rather than defining the closure in the spot we pass it as an argument. +```rust +let expensive_closure = |num: u32| -> u32 { + println!("calculating slowly..."); + thread::sleep(Duration::from_secs(2)); + num +}; +``` +With the type annotations added the closure now looks more similar to a function. + +In the next example we define a function that 1 to its paramamter and a closure that has a same behavior ofr comparison + +This example adds some spaces to line up with the relevant parts. + +This should illustrate how closure syntax is similar to function syntax expect for the use of pipes an the amount of syntax that is optional +```rust +fn add_one_v1 (x: u32) -> u32 { x + 1 } +let add_one_v2 = |x: u32| -> u32 { x + 1 }; +let add_one_v3 = |x| { x + 1 }; +let add_one_v4 = |x| x + 1 ; +``` +The first line shows the function deinition + +The second line shows a fully annotated closure + +The third line the type annotations are removed from the closure + +The fourth line the brakets are removed which are optional becasue the closure body has only one expression. + +All of these are valid dingitions that will produce the same behavior when they are called + +The `add_one_v3` and `add_one_v4` lines require the closures to be evaluated to be able to compile becasue the tpyes will be inferred from their usage + +The is similar to `let v = Vec::new();` needing either type annotations or values of some type to be inserted into the `Vec` for rust to be able to infer the type. + +For closure definitions, the complier will infer one concrete typ for each of their parameters and their return value + +An example of this is below +This shows the definition of a short closure that reutnrs the value it receives as a parameter. (very useless but good to examine) + +Note that we havent added any type annoations to the definition + +Becasue there are tpye annotations we can call the closure with any type. + +We have done this with a `String` first but when we try to call it again with a `integer` we will get an error +```rust +let example_closure = |x| x; + +let s = example_closure(String::from("hello")); +let n = example_closure(5); +``` + +The compiler gives us this error +``` +$ cargo run + Compiling closure-example v0.1.0 (file:///projects/closure-example) +error[E0308]: mismatched types + --> src/main.rs:5:29 + | +5 | let n = example_closure(5); + | --------------- ^- help: try using a conversion method: `.to_string()` + | | | + | | expected `String`, found integer + | arguments to this function are incorrect + | +note: expected because the closure was earlier called with an argument of type `String` + --> src/main.rs:4:29 + | +4 | let s = example_closure(String::from("hello")); + | --------------- ^^^^^^^^^^^^^^^^^^^^^ expected because this argument is of type `String` + | | + | in this closure call +note: closure parameter defined here + --> src/main.rs:2:28 + | +2 | let example_closure = |x| x; + | ^ + +For more information about this error, try `rustc --explain E0308`. +error: could not compile `closure-example` (bin "closure-example") due to 1 previous error +``` +This is becasue when we call `example_closure` with a `String` value, the compiler infer the tpye of `x` and the return tpye of the closure to be `String`. + +These types are then locked into the closure. + +We then get a type error when we next try to use a different type with the same closure. + +## Capturing References or Moving Ownership +Closures can capture value fom their environment in three ways, these are the same ways a function can take a parameter +- borrowing immutably +- borrowing mutably +- taking ownership + +The closure will decide which of these to use based on what the vosy of the function does ith the vaptured values + +In this example a closure is defined that captures an immutable reference to the vector named `list` because it only needs an immutable reference to print the value +```rust +fn main() { + let list = vec![1, 2, 3]; + println!("Before defining closure: {list:?}"); + + let only_borrows = || println!("From closure: {list:?}"); + + println!("Before calling closure: {list:?}"); + only_borrows(); + println!("After calling closure: {list:?}"); +} +``` + +This also illustrates that a variable can bind to a closure definition. We can then later call the closure by using the varaible name nad parntheses as if the variable name we a function name. + +Becasue we can have multiple immutable references to `list` at the same time + +The `list` is still accessible form the code before the closure definition + +After the closure definition but before the closure is called, and after the closure is called. + +Here is what the output is from this example +``` +$ cargo run + Locking 1 package to latest compatible version + Adding closure-example v0.1.0 (/Users/chris/dev/rust-lang/book/tmp/listings/ch13-functional-features/listing-13-04) + Compiling closure-example v0.1.0 (file:///projects/closure-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s + Running `target/debug/closure-example` +Before defining closure: [1, 2, 3] +Before calling closure: [1, 2, 3] +From closure: [1, 2, 3] +After calling closure: [1, 2, 3] +``` + +We change the closure body so that it adds an element to the `list` vector + +The closure now captures a mutalbe reference +```rust +fn main() { + let mut list = vec![1, 2, 3]; + println!("Before defining closure: {list:?}"); + + let mut borrows_mutably = || list.push(7); + + borrows_mutably(); + println!("After calling closure: {list:?}"); +} +``` +Here is the output +``` +$ cargo run + Locking 1 package to latest compatible version + Adding closure-example v0.1.0 (/Users/chris/dev/rust-lang/book/tmp/listings/ch13-functional-features/listing-13-05) + Compiling closure-example v0.1.0 (file:///projects/closure-example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s + Running `target/debug/closure-example` +Before defining closure: [1, 2, 3] +After calling closure: [1, 2, 3, 7] +``` +Note that ther is no longer a `println!` between the definition and the call of the `borrows_mutably` closure. + +When `borrows_mutably` is defined, it captures a mutable reference to `list`. + +We dont use the closure again afer it is called, so the mutable borrow ends. + +Between the closure definition and the closure is called, an immutable borrow to print isn't allowed because no other borrows are allowed when there is a mutable borrow that is valid. + +You will get a invalid reference if yo try to use `println!` in that mutable reference valid section. + +If you want to force a closure to take ownership of the values it used in the environment even though the body of the closure doesn't strictly need ownership. + +You can use the `move` keyword before the parameter list + +This is mostly useful whne passing a closure to a new thread to move the data so that it is owned by the new thread. + +This will be explored later in the concurrency chapter. + +For now let's briefly explore spawning a new thread using a closure that needs the `move` keyword + +Here is the example form above modified to print the vector in a new thread rather than in the main thread +```rust +use std::thread; + +fn main() { + let list = vec![1, 2, 3]; + println!("Before defining closure: {list:?}"); + + thread::spawn(move || println!("From thread: {list:?}")) + .join() + .unwrap(); +} +``` +Here we spawn a new thread, giving the thread a closure to run as an argument. + +The closure body prints out the list + +In the example from before the closure only captured `list` using an immutable reference because that is the least amount of access to `list` needed to print it + +In this example, even though the closure body still only needs an immutable reference, we need to specify that `list` should be moved into the closure by putting the `move` keyword at the beginning of the closure definition. + +The new thread might finish before the ret of the main thread finished or the main threa might finish first. + +If the main thread kept ownership of `list` but ended befreo the new thread did and dropped `list`, the immutable reference in the thread would be invalid. + +The compiler requires that `list` be moved into the cloure given to the new thread so the reference will be valid. + +## Moving Captured Values Out of Closures and the `Fn` Traits +Once a closure has captured a reference or captured ownership of a vlaue from the environment where the closure is defined, which affects what, if anything, is moved *into* the closure + +The body of the closure defines what happens to the references or values when the closure is evaluated later, this effects what, if anything, is moved *out* of the closure + +A closure body can do any of these things: +- Move a captured value out of the closure +- Mutate the captured value +- Neither move nore mutate the value +- Capture nothing from the environment to begin with + +The way a closure captures and handles values from the environment affects which traits the closure implements and traits are how functions and structs can specifty what kids of closures they are use. + +Closures will automatically implement one, two or all three of these `Fn` traits in an additive fushion depending on how the closure's body handles the values: + +1. `FnOnce` applies to closure that can be called one. +- All closures implement at least this treat because all closures can be called. +- A closure that moves captured values out of its body will only implement `FnOnce` and none of the other `Fn` traits because it can only be called once. +2. `FnMut` applies to closures that don't move captured values out of their body +- They might mutate the captured values. +- These closures can be called more than once. +3. `Fn` applies to closures that don't move captured values out of their body and that don't mutate captured values, as well as closures that capture nothing from their environment. +- These closures can be called mroe than once without mututating their environment, this is important in cases such as calling a closure multiple times concurrently + +Lets look at the definition of the `unwrap_or_else` method on `Option` +```rust +impl Option { + pub fn unwrap_or_else(self, f: F) -> T + where + F: FnOnce() -> T + { + match self { + Some(x) => x, + None => f(), + } + } +} +``` +Notice that the `unwrap_or_else` function has the additional generic type parameter `F` + +The `F` type is the type of the paramter named `f`, wihch is the closure we provide when calling `unwrap_or_else` + +The trait bound specified on the generic `F` is `FnOnce() -> T` which means `F` must be able to be called once, take no arguements, and return a `T` + +Using `FnOnce` in the trait bound expresses the constraint that `unwrap_or_else` is going to call `f` at most one time. + +In the body of `unwrap_or_else`, we can see that if the `Option` is `Some`, `f` won't be called. + +If the `Option` is `None`, `f` will be called once + +Becasue all closures implement `FnOnce`, `unwrap_or_else` accpets all three kinds of closures and is as flexible as it can be. + +Note Functions can implement all three of te `Fn` traits too. + +If what we want to do doesn't require capturing a value from the environment, we can use the nsame of a function rather than a closure where we need something that implements one of the `Fn` traits + +For eample on an `Option>` value, we could call `unwrap_or_else(Vec::new)` to get a new, empty vector if the value is `None` + +Lets look at the std library method `sort_by_key` defined on slices, to see how that differes from `unwrap_or_else` and why `sort_by_key` uses `FnMut` instead of `FnOnce` for the trait bound. + +The closure gets one arg in the form of a reference to the current item in the slice being considered and reutrns a value of a tpye `K` that can be ordered. + +This function is usefil when you want to sort a slice by a particular attribute of each item. + +In this example we have a list of `Rectangle` instances and we use `sort_by_key` to order them by their `width` attribute from low to high +```rust +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + list.sort_by_key(|r| r.width); + println!("{list:#?}"); +} +``` +The code outputs +``` +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.41s + Running `target/debug/rectangles` +[ + Rectangle { + width: 3, + height: 5, + }, + Rectangle { + width: 7, + height: 12, + }, + Rectangle { + width: 10, + height: 1, + }, +] +``` + +The reason `sort_by_key` is defined to take an `FnMut` closure is that it calls the closure multiple times. + +Once for each item in the slice. + +The closure `|r| r.width` doesn't capture, mutate, or move out anything from its environment, so it meets the requiremnts of the `FnMut` trait bound prequirements. + +In constrast this example shows a closure that implements just the `FnOnce` trait, because it moves a value out of the environment. + +The compiler wont let us use this closure with `sort_by_key` so it throws a compilation error +```rust +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + let mut sort_operations = vec![]; + let value = String::from("closure called"); + + list.sort_by_key(|r| { + sort_operations.push(value); + r.width + }); + println!("{list:#?}"); +} +``` +This is a contrived, convoluted way to try and count the number of times `sort_by_key` calls the closure when sorting `list`. + +This code attempts to do this counting by pushing `value`m a `String` from the closure's environment, into the `sort_operations` vector + +The closure captures `value` then moves `value` out of the closure by transferring ownership of `value` to the `sort_operations` vector. + +This closure can be called once, tring to call it a second time won't work because `value` would no longer be in the environment to be pushed into `sort_operations` again + +This means that this closure only implements `FnOnce` + +When we try to compile this, we ge this error that `value` can't be moved out of the closure becasue the closure must implemnt `FnMut` + +Here is the output +``` +$ cargo run + Compiling rectangles v0.1.0 (file:///projects/rectangles) +error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure + --> src/main.rs:18:30 + | +15 | let value = String::from("closure called"); + | ----- captured outer variable +16 | +17 | list.sort_by_key(|r| { + | --- captured by this `FnMut` closure +18 | sort_operations.push(value); + | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait + | +help: consider cloning the value if the performance cost is acceptable + | +18 | sort_operations.push(value.clone()); + | ++++++++ + +For more information about this error, try `rustc --explain E0507`. +error: could not compile `rectangles` (bin "rectangles") due to 1 previous error +``` + +The error inidcates that the line in the closure body that moves `value` out of the environment + +To fix this we need to change the closure body so that it doesn't move values out of the environment. + +To count te number of times the closure is called, keeping a counter in the environment and incrementing its value in the closure body is a more straightforward way to calculate that. + +Here is a closure that works with `sort_by_key` because it is only capturing a mutable reference ot the `num_sort_operations` counter and can therefore be called more than once +```rust +#[derive(Debug)] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let mut list = [ + Rectangle { width: 10, height: 1 }, + Rectangle { width: 3, height: 5 }, + Rectangle { width: 7, height: 12 }, + ]; + + let mut num_sort_operations = 0; + list.sort_by_key(|r| { + num_sort_operations += 1; + r.width + }); + println!("{list:#?}, sorted in {num_sort_operations} operations"); +} +``` + +The `Fn` traits are important whne defining or using functions or tpyes that make use of closures. + +Many iterator methods take closure args, so they are improtnat to keep in mind \ No newline at end of file