mirror of
https://github.com/darkicewolf50/RustBrock.git
synced 2025-06-15 13:04:18 -06:00
This commit is contained in:
parent
f2e65b3550
commit
f921ed2326
432
Closures.md
432
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.
|
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<ShirtColor>` and call the `unwrap_or_else` method on `user_preference`
|
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`
|
||||||
|
|
||||||
@ -126,3 +126,433 @@ 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.
|
Funcions on the other hand are not able to capture their environment in this way.
|
||||||
|
|
||||||
## Closure Type Inference and Annoation
|
## 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<T>`
|
||||||
|
```rust
|
||||||
|
impl<T> Option<T> {
|
||||||
|
pub fn unwrap_or_else<F>(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<Vec<T>>` 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
|
Loading…
x
Reference in New Issue
Block a user