Updated snapshot of chapter 10

This commit is contained in:
Carol (Nichols || Goulding) 2022-08-20 09:11:14 -04:00 committed by Carol (Nichols || Goulding)
parent 6dd0b64700
commit 859783c5fc
1 changed files with 25 additions and 140 deletions

View File

@ -39,13 +39,13 @@ help.
## Removing Duplication by Extracting a Function
Generics allow us to replace specific types with a placeholder that represents
multiple types to remove code duplication.
Before diving into generics syntax, then, lets first look at how to remove
duplication in a way that doesnt involve generic types by extracting a
function that replaces specific values with a placeholder that represents
multiple values. Then well apply the same technique to extract a generic
function! By looking at how to recognize duplicated code you can extract into a
function, youll start to recognize duplicated code that can use generics.
multiple types to remove code duplication. Before diving into generics syntax,
then, lets first look at how to remove duplication in a way that doesnt
involve generic types by extracting a function that replaces specific values
with a placeholder that represents multiple values. Then well apply the same
technique to extract a generic function! By looking at how to recognize
duplicated code you can extract into a function, youll start to recognize
duplicated code that can use generics.
We begin with the short program in Listing 10-1 that finds the largest number
in a list.
@ -167,12 +167,6 @@ in.
In summary, here are the steps we took to change the code from Listing 10-2 to
Listing 10-3:
<!---
"In summary"?
/JT --->
<!-- I believe "In sum" to be fine, but other people have been confused by it
as well, so I'm ok changing it. /Carol -->
1. Identify duplicate code.
2. Extract the duplicate code into the body of the function and specify the
inputs and return values of that code in the function signature.
@ -255,9 +249,9 @@ the duplication by introducing a generic type parameter in a single function.
To parameterize the types in a new single function, we need to name the type
parameter, just as we do for the value parameters to a function. You can use
any identifier as a type parameter name. But well use `T` because, by
convention, parameter names in Rust are short, often just a letter, and Rusts
type-naming convention is CamelCase. Short for “type,” `T` is the default
choice of most Rust programmers.
convention, type parameter names in Rust are short, often just a letter, and
Rusts type-naming convention is CamelCase. Short for “type,” `T` is the
default choice of most Rust programmers.
When we use a parameter in the body of the function, we have to declare the
parameter name in the signature so the compiler knows what that name means.
@ -314,18 +308,18 @@ doesnt yet compile yet
If we compile this code right now, well get this error:
```
error[E0369]: binary operation `>` cannot be applied to type `T`
error[E0369]: binary operation `>` cannot be applied to type `&T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- T
| ---- ^ ------- &T
| |
| T
| &T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {
| ^^^^^^^^^^^^^^^^^^^^^^
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
| ++++++++++++++++++++++
```
The help text mentions `std::cmp::PartialOrd`, which is a *trait*, and were
@ -339,26 +333,6 @@ suggestion, we restrict the types valid for `T` to only those that implement
`PartialOrd` and this example will compile, because the standard library
implements `PartialOrd` on both `i32` and `char`.
<!---
The wording at the end of the above paragraph feels a little odd. For the
"Youll learn how to specify that a generic type has a particular trait in the
“Traits as Parameters” section." -- the error message above tells you how to
maybe fix it.
Well, it *could* fix it but the way the example is written adds multiple
constraints.
Do we want to leave this example unfinished and move onto other topics for a
bit or revise the example so it's more self-contained, allowing the compiler to
help us and later revisit after we've learned more?
/JT --->
<!-- I've modified the example and explanation just slightly so that only
adding the `PartialOrd` trait as suggested here will fix it completely, perhaps
leaving the reader hanging a little bit less. It's really hard to teach
generics and trait bounds, though, because you can't do much with generics
unless you have trait bounds too (and can't learn why you'd want trait bounds
without knowing about generics). /Carol -->
### In Struct Definitions
We can also define structs to use a generic type parameter in one or more
@ -413,31 +387,6 @@ know that the generic type `T` will be an integer for this instance of
`Point<T>`. Then when we specify 4.0 for `y`, which weve defined to have the
same type as `x`, well get a type mismatch error like this:
<!---
Not sure how or where we might want to call this out, but this is also how
type inference in Rust works. If we don't know the type, we look for how it's
used. That fresh type becomes a concrete type, and any use after that which
is different than we expect becomes an error.
fn main() {
let mut x;
x = 5;
x = 4.0;
}
Also gives:
|
2 | let mut x;
| ----- expected due to the type of this binding
...
5 | x = 4.0;
| ^^^ expected integer, found floating-point number
/JT --->
<!-- Yeah, it's kind of neat trivia, but doesn't really fit here I don't think.
/Carol -->
```
error[E0308]: mismatched types
--> src/main.rs:7:38
@ -545,16 +494,6 @@ fn main() {
}
```
<!---
The above code gives a warning for the unused `y`. Maybe we can print both
`x` and `y`?
/JT --->
<!-- In general, I'm not worried about unused code warnings, there's a lot of
examples that have unused code because they're small examples. I don't think
there's much value in adding a method and printing `y` as well. /Carol -->
Listing 10-9: Implementing a method named `x` on the `Point<T>` struct that
will return a reference to the `x` field of type `T`
@ -650,8 +589,8 @@ method.
### Performance of Code Using Generics
You might be wondering whether there is a runtime cost when using generic type
parameters. The good news is that using generic types won't make your run any
slower than it would with concrete types.
parameters. The good news is that using generic types won't make your program
run any slower than it would with concrete types.
Rust accomplishes this by performing monomorphization of the code using
generics at compile time. *Monomorphization* is the process of turning generic
@ -676,15 +615,6 @@ is `f64`. As such, it expands the generic definition of `Option<T>` into two
definitions specialized to `i32` and `f64`, thereby replacing the generic
definition with the specific ones.
<!---
We may want to be clear in the above it doesn't actually do this, as you
wouldn't be able to write `enum Option_i32` in your code as it would clash.
/JT --->
<!-- I've reworded the last sentence in the above paragraph and the next
sentence to hopefully sidestep the issue JT pointed out. /Carol -->
The monomorphized version of the code looks similar to the following (the
compiler uses different names than what were using here for illustration):
@ -821,8 +751,6 @@ that the trait definition has defined. Instead of adding a semicolon after each
signature, we use curly brackets and fill in the method body with the specific
behavior that we want the methods of the trait to have for the particular type.
<!-- NOTE TO ADD SOME NUMBER INDICATORS HERE IN THE WORD FILES -->
Now that the library has implemented the `Summary` trait on `NewsArticle` and
`Tweet`, users of the crate can call the trait methods on instances of
`NewsArticle` and `Tweet` in the same way we call regular methods. The only
@ -1073,8 +1001,9 @@ we can use a `where` clause, like this:
```
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
where
T: Display + Clone,
U: Clone + Debug,
{
```
@ -1148,11 +1077,6 @@ around how the `impl Trait` syntax is implemented in the compiler. Well cover
how to write a function with this behavior in the “Using Trait Objects That
Allow for Values of Different Types” section of Chapter 17.
<!-- I've removed the whole "Fixing the `largest` Function with Trait Bounds"
section now that the example is slightly different and adding the one trait
bound as the compiler suggests fixed Listing 10-5 earlier. I've also renumbered
the following listings. /Carol-->
### Using Trait Bounds to Conditionally Implement Methods
By using a trait bound with an `impl` block that uses generic type parameters,
@ -1234,26 +1158,6 @@ generics.
## Validating References with Lifetimes
<!---
meta comment: this chapter is already pretty hefty. We just went through both
generics and a whirlwind tour of traits. Lifetimes, while related to generics,
feel like you might want to give a five minute break between them, let those
sink in, and then pick up this topic.
I noticed a couple topics we may want to touch on above for a bit of
completeness:
* A closer look at how From/Into work and how they relate to each other.
* Using traits to specialize what you do when returning values.
i.e., Why does `let four: u32 = "4".parse().unwrap();` work?
* Turbofish
/JT --->
<!-- These comments are totally valid, but seeing as this revision is already
dragging on later than we were hoping, I don't really want to do large scale
reorganization at this point. /Carol -->
Lifetimes are another kind of generic that weve already been using. Rather
than ensuring that a type has the behavior we want, lifetimes ensure that
references are valid as long as we need them to be.
@ -1443,7 +1347,7 @@ error[E0106]: missing lifetime specifier
help: consider introducing a named lifetime parameter
|
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
| ^^^^ ^^^^^^^ ^^^^^^^ ^^^
| ++++ ++ ++ ++
```
The help text reveals that the return type needs a generic lifetime parameter
@ -1492,20 +1396,11 @@ annotations are meant to tell Rust how generic lifetime parameters of multiple
references relate to each other. Lets examine how the lifetime annotations
relate to each other in the context of the `longest` function.
<!---
The above description is a little hard to follow with a code example.
/JT --->
<!-- Rather than fleshing out the code that goes with this description, I've
moved some of the description to the next section to go with the code example
there. /Carol -->
### Lifetime Annotations in Function Signatures
To use lifetime annotations in function signatures, we need to declare the
generic *lifetime* parameters inside angle brackets between the function name
and the parameter list, just as we did with generic *type* parameters
and the parameter list, just as we did with generic *type* parameters.
We want the signature to express the following constraint: the returned
reference will be valid as long as both the parameters are valid. This is the
@ -1624,7 +1519,7 @@ error[E0597]: `string2` does not live long enough
--> src/main.rs:6:44
|
6 | result = longest(string1.as_str(), string2.as_str());
| ^^^^^^^ borrowed value does not live long enough
| ^^^^^^^^^^^^^^^^ borrowed value does not live long enough
7 | }
| - `string2` dropped here while still borrowed
8 | println!("The longest string is {}", result);
@ -1693,14 +1588,11 @@ lifetime is not related to the lifetime of the parameters at all. Here is the
error message we get:
```
error[E0515]: cannot return value referencing local variable `result`
error[E0515]: cannot return reference to local variable `result`
--> src/main.rs:11:5
|
11 | result.as_str()
| ------^^^^^^^^^
| |
| returns a value referencing data owned by the current function
| `result` is borrowed here
| ^^^^^^^^^^^^^^^ returns a reference to data owned by the current function
```
The problem is that `result` goes out of scope and gets cleaned up at the end
@ -1723,13 +1615,6 @@ hold references, but in that case we would need to add a lifetime annotation on
every reference in the structs definition. Listing 10-24 has a struct named
`ImportantExcerpt` that holds a string slice.
<!---
nit: "So far, the structs we've *defined* all hold owned types"
/JT --->
<!-- Fixed! /Carol -->
Filename: src/main.rs
```