mirror of https://github.com/rust-lang/book
Start of better Fn traits explanation
This commit is contained in:
parent
ce4da5ccbf
commit
5e58b9cc3f
|
@ -60,6 +60,7 @@ BoxMeUp
|
|||
BTreeSet
|
||||
BuildHasher
|
||||
Cacher
|
||||
cacher
|
||||
Cagain
|
||||
callsite
|
||||
CamelCase
|
||||
|
|
|
@ -7,6 +7,11 @@ closures can capture values from the scope in which they’re defined. We’ll
|
|||
demonstrate how these closure features allow for code reuse and behavior
|
||||
customization.
|
||||
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<a id="creating-an-abstraction-of-behavior-with-closures"></a>
|
||||
<a id="refactoring-using-functions"></a>
|
||||
<a id="refactoring-with-closures-to-store-code"></a>
|
||||
|
||||
### Capturing the Environment with Closures
|
||||
|
||||
The first aspect of closures we're going to examine is that closures can
|
||||
|
@ -224,8 +229,6 @@ closure definition and the closure call, an immutable borrow to print isn't
|
|||
allowed because no other borrows are allowed when there's a mutable borrow. Try
|
||||
adding a `println!` there to see what error message you get!
|
||||
|
||||
|
||||
|
||||
If you want to force the closure to take ownership of the values it uses 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
|
||||
|
@ -233,13 +236,221 @@ technique is mostly useful when passing a closure to a new thread to move the
|
|||
data so it’s owned by the new thread. We’ll have more examples of `move`
|
||||
closures in Chapter 16 when we talk about concurrency.
|
||||
|
||||
### Creating an Abstraction of Behavior with Closures
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<a id="storing-closures-using-generic-parameters-and-the-fn-traits"></a>
|
||||
<a id="limitations-of-the-cacher-implementation"></a>
|
||||
|
||||
#### Refactoring Using Functions
|
||||
### Moving Captured Values Out of the Closure and the `Fn` Traits
|
||||
|
||||
#### Refactoring with Closures to Store Code
|
||||
Once a closure has captured a reference or moved a value into the closure, the
|
||||
code in the body of the function also affects what happens to the references or
|
||||
values as a result of calling the function. A closure body can move a captured
|
||||
value out of the closure, can mutate the captured value, can neither move nor
|
||||
mutate the captured value, or can capture nothing from the environment. The way
|
||||
a closure captures and handles values from the environment affects which traits
|
||||
the closure implements. The traits are how functions and structs can specify
|
||||
what kinds of closures they can use.
|
||||
|
||||
### Storing Closures Using Generic Parameters and the `Fn` Traits
|
||||
Closures will automatically implement one, two, or all three of these `Fn`
|
||||
traits, in an additive fashion:
|
||||
|
||||
1. `FnOnce` applies to closures that can be called at least once. All closures
|
||||
implement this trait, because all closures can be called. If a closure moves
|
||||
captured values out of its body, then that closure only implements `FnOnce`
|
||||
and not any 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, but that 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. These closures can be called more
|
||||
than once without mutating their environment, which is important in cases
|
||||
such as calling a closure multiple times concurrently. Closures that don't
|
||||
capture anything from their environment implement `Fn`.
|
||||
|
||||
> Note: Functions can implement all three of the `Fn` traits too. If what we
|
||||
> want to do doesn’t require capturing a value from the environment, we can use
|
||||
> a function rather than a closure where we need something that implements one
|
||||
> of the `Fn` traits.
|
||||
|
||||
Let's look at the definition of the `unwrap_or_else` method on `Option<T>` that
|
||||
we used in Listing 13-x:
|
||||
|
||||
```rust,ignore
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Recall that `T` is the generic type representing the type of the value in the
|
||||
`Some` variant of an `Option`. That type `T` is also the return type of the
|
||||
`unwrap_or_else` function: code that calls `unwrap_or_else` on an
|
||||
`Option<String>`, for example, will get a `String`.
|
||||
|
||||
Next, notice that the `unwrap_or_else` function has an additional generic type
|
||||
parameter, `F`. The `F` type is the type of the parameter named `f`, which is
|
||||
the closure we provide when calling `unwrap_or_else`.
|
||||
|
||||
The trait bound specified on the generic type `F` is `FnOnce() -> T`, which
|
||||
means `F` must be able to be called at least once, take no arguments, and
|
||||
return a `T`. Using `FnOnce` in the trait bound expresses the constraint that
|
||||
`unwrap_or_else` is only 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. Because all
|
||||
closures implement `FnOnce`, `unwrap_or_else` accepts the most different kinds
|
||||
of closures and is as flexible as it can be.
|
||||
|
||||
Now let's look at the standard library method, `sort_by_key` defined on slices,
|
||||
that takes a closure that implements `FnMut` to see how that differs. The
|
||||
`sort_by_key` method takes a closure that gets one argument, a reference to the
|
||||
current item in the slice being considered, and returns a value of type `K`
|
||||
that can be ordered. This function is useful when you want to sort a slice by a
|
||||
particular attribute of each item. In Listing 13-x, 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);
|
||||
}
|
||||
```
|
||||
|
||||
This code prints:
|
||||
|
||||
```console
|
||||
[
|
||||
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 trait bound requirements.
|
||||
|
||||
In contrast, here's an example of a closure that only implements `FnOnce`
|
||||
because it moves a value out of the environment. The compiler won't let us use
|
||||
this closure with `sort_by_key`:
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
#[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("by key called");
|
||||
|
||||
list.sort_by_key(|r| {
|
||||
sort_operations.push(value);
|
||||
r.width
|
||||
});
|
||||
println!("{:#?}", list);
|
||||
}
|
||||
```
|
||||
|
||||
This is a contrived, convoluted way (that doesn't work) to try and count the
|
||||
number of times `sort_by_key` gets called when sorting `list`. This code
|
||||
attempts to do this counting by pushing `value`, 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; trying to call
|
||||
it a second time wouldn't work because `value` would no longer be in the
|
||||
environment to be pushed into `sort_operations` again! Therefore, this closure
|
||||
only implements `FnOnce`. When we try to compile this code, we get this error
|
||||
that `value` can't be moved out of the closure because the closure must
|
||||
implement `FnMut`:
|
||||
|
||||
```console
|
||||
error[E0507]: cannot move out of `value`, a captured variable in an `FnMut` closure
|
||||
--> src/main.rs:18:30
|
||||
|
|
||||
15 | let value = String::from("by key called");
|
||||
| ----- captured outer variable
|
||||
16 |
|
||||
17 | list.sort_by_key(|r| {
|
||||
| ______________________-
|
||||
18 | | sort_operations.push(value);
|
||||
| | ^^^^^ move occurs because `value` has type `String`, which does not implement the `Copy` trait
|
||||
19 | | r.width
|
||||
20 | | });
|
||||
| |_____- captured by this `FnMut` closure
|
||||
```
|
||||
|
||||
The error points to 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. If we're interested in the number of times
|
||||
`sort_by_key` is called, keeping a counter in the environment and incrementing
|
||||
its value in the closure body is a more straightforward way to calculate that.
|
||||
This closure works with `sort_by_key` because it is only capturing a mutable
|
||||
reference to 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!("{:#?}, sorted in {num_sort_operations} operations", list);
|
||||
}
|
||||
```
|
||||
|
||||
The `Fn` traits are important when defining or using functions or types that
|
||||
make use of closures. The next section discusses iterators, and many iterator
|
||||
methods take closure arguments. Keep these details of closures in mind as we
|
||||
explore iterators!
|
||||
|
||||
[unwrap-or-else]: ../std/option/enum.Option.html#method.unwrap_or_else
|
||||
|
|
|
@ -181,3 +181,42 @@ Because `map` takes a closure, we can specify any operation we want to perform
|
|||
on each item. This is a great example of how closures let you customize some
|
||||
behavior while reusing the iteration behavior that the `Iterator` trait
|
||||
provides.
|
||||
|
||||
### Using Closures that Capture Their Environment
|
||||
|
||||
Now that we’ve introduced iterators, we can demonstrate a common use of
|
||||
closures that capture their environment by using the `filter` iterator adaptor.
|
||||
The `filter` method on an iterator takes a closure that takes each item from
|
||||
the iterator and returns a Boolean. If the closure returns `true`, the value
|
||||
will be included in the iterator produced by `filter`. If the closure returns
|
||||
`false`, the value won’t be included in the resulting iterator.
|
||||
|
||||
In Listing 13-19, we use `filter` with a closure that captures the `shoe_size`
|
||||
variable from its environment to iterate over a collection of `Shoe` struct
|
||||
instances. It will return only shoes that are the specified size.
|
||||
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">Listing 13-19: Using the `filter` method with a closure
|
||||
that captures `shoe_size`</span>
|
||||
|
||||
The `shoes_in_size` function takes ownership of a vector of shoes and a shoe
|
||||
size as parameters. It returns a vector containing only shoes of the specified
|
||||
size.
|
||||
|
||||
In the body of `shoes_in_size`, we call `into_iter` to create an iterator
|
||||
that takes ownership of the vector. Then we call `filter` to adapt that
|
||||
iterator into a new iterator that only contains elements for which the closure
|
||||
returns `true`.
|
||||
|
||||
The closure captures the `shoe_size` parameter from the environment and
|
||||
compares the value with each shoe’s size, keeping only shoes of the size
|
||||
specified. Finally, calling `collect` gathers the values returned by the
|
||||
adapted iterator into a vector that’s returned by the function.
|
||||
|
||||
The test shows that when we call `shoes_in_size`, we get back only shoes
|
||||
that have the same size as the value we specified.
|
||||
|
|
|
@ -214,14 +214,13 @@ so it takes the closure it’s given and gives it to an idle thread in the pool
|
|||
to run.
|
||||
|
||||
We’ll define the `execute` method on `ThreadPool` to take a closure as a
|
||||
parameter. Recall from the [“Storing Closures Using Generic Parameters and the
|
||||
`Fn` Traits”][storing-closures-using-generic-parameters-and-the-fn-traits]<!--
|
||||
ignore --> section in Chapter 13 that we can take closures as parameters with
|
||||
three different traits: `Fn`, `FnMut`, and `FnOnce`. We need to decide which
|
||||
kind of closure to use here. We know we’ll end up doing something similar to
|
||||
the standard library `thread::spawn` implementation, so we can look at what
|
||||
bounds the signature of `thread::spawn` has on its parameter. The documentation
|
||||
shows us the following:
|
||||
parameter. Recall from the [“Moving Captured Values Out of the Closure and the
|
||||
`Fn` Traits”][fn-traits]<!-- ignore --> section in Chapter 13 that we can take
|
||||
closures as parameters with three different traits: `Fn`, `FnMut`, and
|
||||
`FnOnce`. We need to decide which kind of closure to use here. We know we’ll
|
||||
end up doing something similar to the standard library `thread::spawn`
|
||||
implementation, so we can look at what bounds the signature of `thread::spawn`
|
||||
has on its parameter. The documentation shows us the following:
|
||||
|
||||
```rust,ignore
|
||||
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
|
||||
|
@ -679,5 +678,5 @@ of the call to `job()`, meaning other workers cannot receive jobs.
|
|||
[creating-type-synonyms-with-type-aliases]:
|
||||
ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases
|
||||
[integer-types]: ch03-02-data-types.html#integer-types
|
||||
[storing-closures-using-generic-parameters-and-the-fn-traits]:
|
||||
ch13-01-closures.html#storing-closures-using-generic-parameters-and-the-fn-traits
|
||||
[fn-traits]:
|
||||
ch13-01-closures.html#moving-captured-values-out-of-the-closure-and-the-fn-traits
|
||||
|
|
Loading…
Reference in New Issue