mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-02-03 07:48:41 +08:00
Merge remote-tracking branch 'origin/generics'
This commit is contained in:
commit
38c1c09437
1215
nostarch/chapter10.md
Normal file
1215
nostarch/chapter10.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -45,7 +45,10 @@
|
||||
- [Recoverable Errors with `Result`](ch09-02-recoverable-errors-with-result.md)
|
||||
- [To `panic!` or Not To `panic!`](ch09-03-to-panic-or-not-to-panic.md)
|
||||
|
||||
- [Generics]()
|
||||
- [Generics](ch10-00-generics.md)
|
||||
- [Syntax](ch10-01-syntax.md)
|
||||
- [Traits](ch10-02-traits.md)
|
||||
- [Lifetime syntax](ch10-03-lifetime-syntax.md)
|
||||
|
||||
- [I/O]()
|
||||
- [`Read` & `Write`]()
|
||||
|
130
src/ch10-00-generics.md
Normal file
130
src/ch10-00-generics.md
Normal file
@ -0,0 +1,130 @@
|
||||
# Generics
|
||||
|
||||
One of the core tools a programming language gives you is the ability to deal
|
||||
effectively with duplication of code. It's important to minimize the amount of
|
||||
code that is duplicated throughout a program to make maintenace easier and
|
||||
minimize logic errors. Maintenance will be easier if there's only one place
|
||||
that you need to change the code if you change your mind about how the program
|
||||
should work, rather than multiple places in the code. If your program's logic
|
||||
is duplicated in different places and those places don't match, you'll get
|
||||
errors or unexpected and undesired behavior from your program that could be
|
||||
hard to track down. Rust has the concept of *generics* as one way to eliminate
|
||||
duplicate code. Generics come in the form of generic types, traits that those
|
||||
generic types have, and generic lifetimes. We'll cover how to use all of these
|
||||
in this chapter.
|
||||
|
||||
## Removing Duplication by Extracting a Function
|
||||
|
||||
Let's first go through a technique for dealing with duplication that you're
|
||||
probably familiar with: extracting a function. Consider a small program that
|
||||
finds the largest number in a list, shown in Listing 10-1:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let numbers = vec![34, 50, 25, 100, 65];
|
||||
|
||||
let mut largest = numbers[0];
|
||||
|
||||
for number in numbers {
|
||||
if largest > number {
|
||||
largest = number;
|
||||
}
|
||||
}
|
||||
|
||||
println!("The largest number is {}", largest);
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-1: Code to find the largest number in a list of numbers
|
||||
</caption>
|
||||
|
||||
If we needed to find the largest number in two different lists of numbers, we
|
||||
could duplicate the code in Listing 10-1 and have the same logic exist in two
|
||||
places in the program:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let numbers = vec![34, 50, 25, 100, 65];
|
||||
|
||||
let mut largest = numbers[0];
|
||||
|
||||
for number in numbers {
|
||||
if largest > number {
|
||||
largest = number;
|
||||
}
|
||||
}
|
||||
|
||||
println!("The largest number is {}", largest);
|
||||
|
||||
let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8];
|
||||
|
||||
let mut largest = numbers[0];
|
||||
|
||||
for number in numbers {
|
||||
if largest > number {
|
||||
largest = number;
|
||||
}
|
||||
}
|
||||
|
||||
println!("The largest number is {}", largest);
|
||||
}
|
||||
```
|
||||
|
||||
Copying code is tedious and error-prone, plus now we have two places to update
|
||||
the logic if we need it to change. Rust, like many languages, gives us a way to
|
||||
deal with this duplication by creating an abstraction, and in this case the
|
||||
abstraction we'll use is a function. Here's a program where we've extracted the
|
||||
code in Listing 10-1 that finds the largest number into a function named
|
||||
`largest`. This program can find the largest number in two different lists of
|
||||
numbers, but the code from Listing 10-1 only exists in one spot:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn largest(numbers: Vec<i32>) {
|
||||
let mut largest = numbers[0];
|
||||
|
||||
for number in numbers {
|
||||
if largest > number {
|
||||
largest = number;
|
||||
}
|
||||
}
|
||||
|
||||
println!("The largest number is {}", largest);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let numbers = vec![34, 50, 25, 100, 65];
|
||||
|
||||
largest(numbers);
|
||||
|
||||
let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8];
|
||||
|
||||
largest(numbers);
|
||||
}
|
||||
```
|
||||
|
||||
The function takes an argument, `numbers`, which represents any concrete
|
||||
`Vec<i32>` that we might pass into the function. The code in the function
|
||||
definition operates on the `numbers` representation of any `Vec<i32>`. When
|
||||
we call the `largest` function, the code actually runs on the specific values
|
||||
that we pass in.
|
||||
|
||||
Functions aren't the only way to eliminate duplication. For example, our
|
||||
`largest` function only works for vectors of `i32`. What if we wanted to find
|
||||
the largest number in a list of floats? Or the largest value in some sort of
|
||||
custom `struct` or `enum`? We can't solve those kinds of duplication with
|
||||
regular functions.
|
||||
|
||||
To solve these kinds of problems, Rust provides a feature called *generics*. In
|
||||
the same way that functions allow us to abstract over common code, generics
|
||||
allow us to abstract over types. This ability gives us tremendous power to
|
||||
write code that works in a large number of situations. First, we'll examine the
|
||||
syntax of generics. Then, we'll talk about another feature that's used to
|
||||
augment generics: traits. Finally, we'll discuss one of Rust's most unique uses
|
||||
of generics: lifetimes.
|
369
src/ch10-01-syntax.md
Normal file
369
src/ch10-01-syntax.md
Normal file
@ -0,0 +1,369 @@
|
||||
## Generics Syntax
|
||||
|
||||
We've already hinted at the idea of generics in previous chapters, but we
|
||||
never dug into what exactly they are or how to use them. In places where we
|
||||
specify a type, like function signatures or structs, instead we can use
|
||||
*generics*. Generics are stand-ins that represent an abstract set instead of something concrete. In this section, we're going to cover generic *data types*.
|
||||
|
||||
You can recognize when any kind of generics are used by the way that they fit
|
||||
into Rust's syntax: any time you see angle brackets, `<>`, you're dealing with
|
||||
generics. Types we've seen before, like in Chapter 8 where we discussed vectors
|
||||
with types like `Vec<i32>`, employ generics. The type that the standard library
|
||||
defines for vectors is `Vec<T>`. That `T` is called a *type parameter*, and it
|
||||
serves a similar function as parameters to functions: you fill in the parameter
|
||||
with a concrete type, and that determines how the overall type works. In the
|
||||
same way that a function like `foo(x: i32)` can be called with a specific value
|
||||
such as `foo(5)`, a `Vec<T>` can be created with a specific type, like
|
||||
`Vec<i32>`.
|
||||
|
||||
### Duplicated Enum Definitions
|
||||
|
||||
Let's dive into generic data types in more detail. We learned about how to use
|
||||
the `Option<T>` enum in Chapter 6, but we never examined its definition. Let's
|
||||
try to imagine how we'd write it! We'll start from duplicated code like we did
|
||||
in the "Removing Duplication by Extracting a Function" section. This time,
|
||||
we'll remove the duplication by extracting a generic data type instead of
|
||||
extracting a function, but the mechanics of doing the extraction will be
|
||||
similar. First, let's consider an `Option` enum with a `Some` variant that can
|
||||
only hold an `i32`. We'll call this enum `OptionalNumber`:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
enum OptionalNumber {
|
||||
Some(i32),
|
||||
None,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let number = OptionalNumber::Some(5);
|
||||
let no_number = OptionalNumber::None;
|
||||
}
|
||||
```
|
||||
|
||||
This works just fine for `i32`s. But what if we also wanted to store `f64`s? We
|
||||
would have to duplicate code to define a separate `Option` enum type for each
|
||||
type we wanted to be able to hold in the `Some` variants. For example, here is
|
||||
how we could define and use `OptionalFloatingPointNumber`:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
enum OptionalFloatingPointNumber {
|
||||
Some(f64),
|
||||
None,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let number = OptionalFloatingPointNumber::Some(5.0);
|
||||
let no_number = OptionalFloatingPointNumber::None;
|
||||
}
|
||||
```
|
||||
|
||||
We've made the enum's name a bit long in order to drive the point home. With
|
||||
what we currently know how to do in Rust, we would have to write a unique type
|
||||
for every single kind of value we wanted to have either `Some` or `None` of. In
|
||||
other words, the idea of "an optional value" is a more abstract concept than one
|
||||
specific type. We want it to work for any type at all.
|
||||
|
||||
### Removing Duplication by Extracting a Generic Data Type
|
||||
|
||||
Let's see how to get from duplicated types to the generic type. Here are the
|
||||
definitions of our two enums side-by-side:
|
||||
|
||||
```text
|
||||
enum OptionalNumber { enum OptionalFloatingPointNumber {
|
||||
Some(i32), Some(f64),
|
||||
None, None,
|
||||
} }
|
||||
```
|
||||
|
||||
Aside from the names, we have one line where the two definitions are very
|
||||
close, but still different: the line with the `Some` definitions. The only
|
||||
difference is the type of the data in that variant, `i32` and `f64`.
|
||||
|
||||
Just like we can parameterize arguments to a function by choosing a name, we
|
||||
can parameterize the type by choosing a name. In this case, we've chosen the
|
||||
name `T`. We could choose any identifier here, but Rust style has type
|
||||
parameters follow the same style as types themselves: CamelCase. In addition,
|
||||
they tend to be short, often one letter. `T` is the traditional default choice,
|
||||
short for 'type'. Let's use that name in our `Some` variant definitions where
|
||||
the `i32` and `f64` types were:
|
||||
|
||||
```text
|
||||
enum OptionalNumber { enum OptionalFloatingPointNumber {
|
||||
Some(T), Some(T),
|
||||
None, None,
|
||||
} }
|
||||
```
|
||||
|
||||
There's one problem, though: we've *used* `T`, but not defined it. This would
|
||||
be similar to using an argument to a function in the body without declaring it
|
||||
in the signature. We need to tell Rust that we've introduced a generic
|
||||
parameter. The syntax to do that is the angle brackets, like this:
|
||||
|
||||
```text
|
||||
enum OptionalNumber<T> { enum OptionalFloatingPointNumber<T> {
|
||||
Some(T), Some(T),
|
||||
None, None,
|
||||
} }
|
||||
```
|
||||
|
||||
The `<>`s after the enum name indicate a list of type parameters, just like
|
||||
`()` after a function name indicates a list of value parameters. Now the only
|
||||
difference between our two `enum`s is the name. Since we've made them generic,
|
||||
they're not specific to integers or floating point numbers anymore, so they can
|
||||
have the same name:
|
||||
|
||||
```text
|
||||
enum Option<T> { enum Option<T> {
|
||||
Some(T), Some(T),
|
||||
None, None,
|
||||
} }
|
||||
```
|
||||
|
||||
Now they're identical! We've made our type fully generic. This definition is
|
||||
also how `Option` is defined in the standard library. If we were to read this
|
||||
definition aloud, we'd say, "`Option` is an `enum` with one type parameter,
|
||||
`T`. It has two variants: `Some`, which has a value with type `T`, and `None`,
|
||||
which has no value." We can now use the same `Option` type whether we're holding an `i32` or an `f64`:
|
||||
|
||||
```rust
|
||||
let integer = Option::Some(5);
|
||||
let float = Option::Some(5.0);
|
||||
```
|
||||
|
||||
We've left in the `Option::` namespace for consistency with the previous
|
||||
examples, but since `use Option::*` is in the prelude, it's not needed. Usually
|
||||
using `Option` looks like this:
|
||||
|
||||
```rust
|
||||
let integer = Some(5);
|
||||
let float = Some(5.0);
|
||||
```
|
||||
|
||||
When you recognize situations with almost-duplicate types like this in your
|
||||
code, you can follow this process to reduce duplication using generics.
|
||||
|
||||
### Monomorphization at Compile Time
|
||||
|
||||
Understanding this refactoring process is also useful in understanding how
|
||||
generics work behind the scenes: the compiler does the exact opposite of this
|
||||
process when compiling your code. *Monomorphization* means taking code that
|
||||
uses generic type parameters and generating code that is specific for each
|
||||
concrete type that is used with the generic code. Monomorphization is why
|
||||
Rust's generics are extremely efficient at runtime. Consider this code that
|
||||
uses the standard library's `Option`:
|
||||
|
||||
```rust
|
||||
let integer = Some(5);
|
||||
let float = Some(5.0);
|
||||
```
|
||||
|
||||
When Rust compiles this code, it will perform monomorphization. What this means
|
||||
is the compiler will see that we've used two kinds of `Option<T>`: one where
|
||||
`T` is `i32`, and one where `T` is `f64`. As such, it will expand the generic
|
||||
definition of `Option<T>` into `Option_i32` and `Option_f64`, thereby replacing
|
||||
the generic definition with the specific ones. The more specific version looks
|
||||
like the duplicated code we started with at the beginning of this section:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
enum Option_i32 {
|
||||
Some(i32),
|
||||
None,
|
||||
}
|
||||
|
||||
enum Option_f64 {
|
||||
Some(f64),
|
||||
None,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let integer = Option_i32::Some(5);
|
||||
let float = Option_f64::Some(5.0);
|
||||
}
|
||||
```
|
||||
|
||||
In other words, we can write the non-duplicated form that uses generics in our
|
||||
code, but Rust will compile that into code that acts as though we wrote the
|
||||
specific type out in each instance. This means we pay no runtime cost for using
|
||||
generics; it's just like we duplicated each particular definition.
|
||||
|
||||
### Generic Structs
|
||||
|
||||
In a similar fashion as we did with enums, we can use `<>`s with structs as
|
||||
well in order to define structs that have a generic type parameter in one or
|
||||
more of their fields. Generic structs also get monomorphized into specialized
|
||||
types at compile time. Listing 10-2 shows the definition and use of a `Point`
|
||||
struct that could hold `x` and `y` coordinate values that are any type:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let integer = Point { x: 5, y: 10 };
|
||||
let float = Point { x: 1.0, y: 4.0 };
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-2: A `Point` struct that holds `x` and `y` values of type `T`
|
||||
</caption>
|
||||
|
||||
The syntax is the same with structs: add a `<T>` after the name of the struct,
|
||||
then use `T` in the definition where you want to use that generic type instead
|
||||
of a specific type.
|
||||
|
||||
### Multiple Type Parameters
|
||||
|
||||
Note that in the `Point` definition in Listing 10-2, we've used the same `T`
|
||||
parameter for both fields. This means `x` and `y` must always be values of the
|
||||
same type. Trying to instantiate a `Point` that uses an `i32` for `x` and an
|
||||
`f64` for `y`, like this:
|
||||
|
||||
```rust,ignore
|
||||
let p = Point { x: 5, y: 20.0 };
|
||||
```
|
||||
|
||||
results in a compile-time error that indicates the type of `y` must match the
|
||||
type of `x`:
|
||||
|
||||
```bash
|
||||
error[E0308]: mismatched types
|
||||
|
|
||||
7 | let p = Point { x: 5, y: 20.0 };
|
||||
| ^^^^ expected integral variable, found floating-point variable
|
||||
|
|
||||
= note: expected type `{integer}`
|
||||
= note: found type `{float}`
|
||||
```
|
||||
|
||||
If we need to be able to have fields with generic but different types, we can
|
||||
declare multiple type parameters within the angle brackets, separated by a
|
||||
comma. Listing 10-3 shows how to define a `Point` that can have different types
|
||||
for `x` and `y`:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
struct Point<X, Y> {
|
||||
x: X,
|
||||
y: Y,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let integer = Point { x: 5, y: 10 };
|
||||
let float = Point { x: 1.0, y: 4.0 };
|
||||
let p = Point { x: 5, y: 20.0 };
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-2: A `Point` struct that holds an `x` value of type `X` and a `y`
|
||||
value of type `Y`
|
||||
</caption>
|
||||
|
||||
Now `x` will have the type of `X`, and `y` will have the type of `Y`, and we
|
||||
can instantiate a `Point` with an `i32` for `x` and an `f64` for `y`.
|
||||
|
||||
We can make `enum`s with multiple type parameters as well. Recall the enum
|
||||
`Result<T, E>` from Chapter 9 that we used for recoverable errors. Here's its
|
||||
definition:
|
||||
|
||||
```rust
|
||||
enum Result<T, E> {
|
||||
Ok(T),
|
||||
Err(E),
|
||||
}
|
||||
```
|
||||
|
||||
Each variant stores a different kind of information, and they're both generic.
|
||||
|
||||
You can have as many type parameters as you'd like. Similarly to parameters of
|
||||
values in function signatures, if you have a lot of parameters, the code can
|
||||
get quite confusing, so try to keep the number of parameters defined in any one
|
||||
type small if you can.
|
||||
|
||||
### Generic Functions and Methods
|
||||
|
||||
In a similar way to data structures, we can use the `<>` syntax in function or
|
||||
method definitions. The angle brackets for type parameters go after the
|
||||
function or method name and before the argument list in parentheses:
|
||||
|
||||
```rust
|
||||
fn generic_function<T>(value: T) {
|
||||
// code goes here
|
||||
}
|
||||
```
|
||||
|
||||
We can use the same process that we used to refactor duplicated type
|
||||
definitions using generics to refactor duplicated function definitions using
|
||||
generics. Consider these two side-by-side function signatures that differ in
|
||||
the type of `value`:
|
||||
|
||||
```text
|
||||
fn takes_integer(value: i32) { fn takes_float(value: f64) {
|
||||
// code goes here // code goes here
|
||||
} }
|
||||
```
|
||||
|
||||
We can add a type parameter list that declares the generic type `T` after the
|
||||
function names, then use `T` where the specific `i32` and `f64` types were:
|
||||
|
||||
```text
|
||||
fn takes_integer<T>(value: T) { fn takes_float<T>(value: T) {
|
||||
// code goes here // code goes here
|
||||
} }
|
||||
```
|
||||
|
||||
At this point, only the names differ, so we could unify the two functions into
|
||||
one:
|
||||
|
||||
```rust,ignore
|
||||
fn takes<T>(value: T) {
|
||||
// code goes here
|
||||
}
|
||||
```
|
||||
|
||||
There's one problem though. We've got some function *definitions* that work,
|
||||
but if we try to use `value` in code in the function body, we'll get an
|
||||
error. For example, the function definition in Listing 10-3 tries to print out
|
||||
`value` in its body:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```rust,ignore
|
||||
fn show_anything<T>(value: T) {
|
||||
println!("I have something to show you!");
|
||||
println!("It's: {}", value);
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-3: A `show_anything` function definition that does not yet compile
|
||||
</caption>
|
||||
|
||||
Compiling this definition results in an error:
|
||||
|
||||
```bash
|
||||
error[E0277]: the trait bound `T: std::fmt::Display` is not satisfied
|
||||
--> <anon>:3:37
|
||||
|
|
||||
3 | println!("It's: {}", value);
|
||||
| ^^^^^ trait `T: std::fmt::Display` not satisfied
|
||||
|
|
||||
= help: consider adding a `where T: std::fmt::Display` bound
|
||||
= note: required by `std::fmt::Display::fmt`
|
||||
|
||||
error: aborting due to previous error(s)
|
||||
```
|
||||
|
||||
This error mentions something we haven't learned about yet: traits. In the next
|
||||
section, we'll learn how to make this compile.
|
289
src/ch10-02-traits.md
Normal file
289
src/ch10-02-traits.md
Normal file
@ -0,0 +1,289 @@
|
||||
## Traits
|
||||
|
||||
*Traits* are similar to a feature often called 'interfaces' in other languages,
|
||||
but are also different. Traits let us do another kind of abstraction: they let
|
||||
us abstract over *behavior* that types can have in common.
|
||||
|
||||
When we use a generic type parameter, we are telling Rust that any type is
|
||||
valid in that location. When other code *uses* a value that could be of any
|
||||
type, we need to also tell Rust that the type has the functionality that we
|
||||
need. Traits let us specify that, for example, we need any type `T` that has
|
||||
methods defined on it that allow us to print a value of that type. This is
|
||||
powerful because we can still leave our definitions generic to allow use of
|
||||
many different types, but we can constrain the type at compile-time to types
|
||||
that have the behavior we need to be able to use.
|
||||
|
||||
Here's an example definition of a trait named `Printable` that has a method
|
||||
named `print`:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```rust
|
||||
trait Printable {
|
||||
fn print(&self);
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-4: A `Printable` trait definition with one method, `print`
|
||||
</caption>
|
||||
|
||||
We declare a trait with the `trait` keyword, then the trait's name. In this
|
||||
case, our trait will describe types which can be printed. Inside of curly
|
||||
braces, we declare a method signature, but instead of providing an
|
||||
implementation inside curly braces, we put a semicolon after the signature. A
|
||||
trait can have multiple methods in its body, with the method signatures listend one per line and each line ending in a semicolon.
|
||||
|
||||
Implementing a trait for a particular type looks similar to implementing
|
||||
methods on a type since it's also done with the `impl` keyword, but we specify
|
||||
the trait name as well. Inside the `impl` block, we specify definitions for the
|
||||
trait's methods in the context of the specific type. Listing 10-5 has an
|
||||
example of implementing the `Printable` trait from Listing 10-4 (that only has
|
||||
the `print` method) for a `Temperature` enum:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```rust
|
||||
# trait Printable {
|
||||
# fn print(&self);
|
||||
# }
|
||||
#
|
||||
enum Temperature {
|
||||
Celsius(i32),
|
||||
Fahrenheit(i32),
|
||||
}
|
||||
|
||||
impl Printable for Temperature {
|
||||
fn print(&self) {
|
||||
match *self {
|
||||
Temperature::Celsius(val) => println!("{}°C", val),
|
||||
Temperature::Fahrenheit(val) => println!("{}°F", val),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-5: Implementing the `Printable` trait on a `Temperature` enum
|
||||
</caption>
|
||||
|
||||
In the same way `impl` lets us define methods, we've used it to define methods
|
||||
that pertain to our trait. We can call methods that our trait has defined just
|
||||
like we can call other methods:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
# trait Printable {
|
||||
# fn print(&self);
|
||||
# }
|
||||
#
|
||||
# enum Temperature {
|
||||
# Celsius(i32),
|
||||
# Fahrenheit(i32),
|
||||
# }
|
||||
#
|
||||
# impl Printable for Temperature {
|
||||
# fn print(&self) {
|
||||
# match *self {
|
||||
# Temperature::Celsius(val) => println!("{}°C", val),
|
||||
# Temperature::Fahrenheit(val) => println!("{}°F", val),
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
fn main() {
|
||||
let t = Temperature::Celsius(37);
|
||||
|
||||
t.print();
|
||||
}
|
||||
```
|
||||
|
||||
Note that in order to use a trait's methods, the trait itself must be in scope.
|
||||
If the definition of `Printable` was in a module, the definition would need to
|
||||
be defined as `pub` and we would need to `use` the trait in the scope where we
|
||||
wanted to call the `print` method. This is because it's possible to have two
|
||||
traits that both define a method named `print`, and our `Temperature` enum might
|
||||
implement both. Rust wouldn't know which `print` method we wanted unless we
|
||||
brought the trait we wanted into our current scope with `use`.
|
||||
|
||||
### Trait Bounds
|
||||
|
||||
Defining traits with methods and implementing the trait methods on a particular
|
||||
type gives Rust more information than just defining methods on a type directly.
|
||||
The information Rust gets is that the type that implements the trait can be
|
||||
used in places where the code specifies that it needs some type that implements
|
||||
a trait. To illustrate this, Listing 10-6 has a `print_anything` function
|
||||
definition. This is similar to the `show_anything` function from Listing 10-3,
|
||||
but this function has a *trait bound* on the generic type `T` and uses the
|
||||
`print` function from the trait. A trait bound constrains the generic type to
|
||||
be any type that implements the trait specified, instead of any type at all.
|
||||
With the trait bound, we're then allowed to use the trait method `print` in the
|
||||
function body:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```rust
|
||||
# trait Printable {
|
||||
# fn print(&self);
|
||||
# }
|
||||
#
|
||||
fn print_anything<T: Printable>(value: T) {
|
||||
println!("I have something to print for you!");
|
||||
value.print();
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-6: A `print_anything` function that uses the trait bound `Printable`
|
||||
on type `T`
|
||||
</caption>
|
||||
|
||||
Trait bounds are specified in the type name declarations within the angle
|
||||
brackets. After the name of the type that you want to apply the bound to, add a
|
||||
colon (`:`) and then specify the name of the trait. This function now specifies
|
||||
that it takes a `value` parameter that can be of any type, as long as that type
|
||||
implements the trait `Printable`. We need to specify the `Printable` trait in
|
||||
the type name declarations because we want to be able to call the `print`
|
||||
method that is part of the `Printable` trait.
|
||||
|
||||
Now we are able to call the `print_anything` function from Listing 10-6 and
|
||||
pass it a `Temperature` instance as the `value` parameter, since we implemented
|
||||
the trait `Printable` on `Temperature` in Listing 10-5:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
# trait Printable {
|
||||
# fn print(&self);
|
||||
# }
|
||||
#
|
||||
# enum Temperature {
|
||||
# Celsius(i32),
|
||||
# Fahrenheit(i32),
|
||||
# }
|
||||
#
|
||||
# impl Printable for Temperature {
|
||||
# fn print(&self) {
|
||||
# match *self {
|
||||
# Temperature::Celsius(val) => println!("{}°C", val),
|
||||
# Temperature::Fahrenheit(val) => println!("{}°F", val),
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# fn print_anything<T: Printable>(value: T) {
|
||||
# println!("I have something to print for you!");
|
||||
# value.print();
|
||||
# }
|
||||
#
|
||||
fn main() {
|
||||
let temperature = Temperature::Fahrenheit(98);
|
||||
print_anything(temperature);
|
||||
}
|
||||
```
|
||||
|
||||
If we implement the `Printable` trait on other types, we can use them with the
|
||||
`print_anything` method too. If we try to call `print_anything` with an `i32`,
|
||||
which does *not* implement the `Printable` trait, we get a compile-time error
|
||||
that looks like this:
|
||||
|
||||
```bash
|
||||
error[E0277]: the trait bound `{integer}: Printable` is not satisfied
|
||||
|
|
||||
29 | print_anything(3);
|
||||
| ^^^^^^^^^^^^^^ trait `{integer}: Printable` not satisfied
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
= help: <Point as Printable>
|
||||
= note: required by `print_anything`
|
||||
```
|
||||
|
||||
Traits are an extremely useful feature of Rust. You'll almost never see generic
|
||||
functions without an accompanying trait bound. There are many traits in the
|
||||
standard library, and they're used for many, many different things. For
|
||||
example, our `Printable` trait is similar to one of those traits, `Display`.
|
||||
And in fact, that's how `println!` decides how to format things with `{}`. The
|
||||
`Display` trait has a `fmt` method that determines how to format something.
|
||||
|
||||
Listing 10-7 shows our original example from Listing 10-3, but this time using
|
||||
the standard library's `Display` trait in the trait bound on the generic type
|
||||
in the `show_anything` function:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```rust
|
||||
use std::fmt::Display;
|
||||
|
||||
fn show_anything<T: Display>(value: T) {
|
||||
println!("I have something to show you!");
|
||||
println!("It's: {}", value);
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-7: The `show_anything` function with trait bounds
|
||||
</caption>
|
||||
|
||||
Now that this function specifies that `T` can be any type as long as that type
|
||||
implements the `Display` trait, this code will compile.
|
||||
|
||||
### Multiple Trait Bounds and `where` Syntax
|
||||
|
||||
Each generic type can have its own trait bounds. The signature for a function
|
||||
that takes a type `T` that implements `Display` and a type `U` that implements
|
||||
`Printable` looks like:
|
||||
|
||||
```rust,ignore
|
||||
fn some_function<T: Display, U: Printable>(value: T, other_value: U) {
|
||||
```
|
||||
|
||||
To specify multiple trait bounds on one type, list the trait bounds in a list
|
||||
with a `+` between each trait. For example, here's the signature of a function
|
||||
that takes a type `T` that implements `Display` and `Clone` (which is another
|
||||
standard library trait we have mentioned):
|
||||
|
||||
```rust,ignore
|
||||
fn some_function<T: Display + Clone>(value: T) {
|
||||
```
|
||||
|
||||
When trait bounds start getting complicated, there is another syntax that's a
|
||||
bit cleaner: `where`. And in fact, the error we got when we ran the code from
|
||||
Listing 10-3 referred to it:
|
||||
|
||||
```bash
|
||||
help: consider adding a `where T: std::fmt::Display` bound
|
||||
```
|
||||
|
||||
The `where` syntax moves the trait bounds after the function arguments list.
|
||||
This definition of `show_anything` means the exact same thing as the definition
|
||||
in Listing 10-7, just said a different way:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```rust
|
||||
use std::fmt::Display;
|
||||
|
||||
fn show_anything<T>(value: T) where T: Display {
|
||||
println!("I have something to show you!");
|
||||
println!("It's: {}", value);
|
||||
}
|
||||
```
|
||||
|
||||
Instead of `T: Display` going inside the angle brackets, they go after the
|
||||
`where` keyword at the end of the function signature. This can make complex
|
||||
signatures easier to read. The `where` clause and its parts can also go on new
|
||||
lines. Here's the signature of a function that takes three generic type
|
||||
parameters that each have multiple trait bounds:
|
||||
|
||||
```rust,ignore
|
||||
fn some_function<T, U, V>(t: T, u: U, v: V)
|
||||
where T: Display + Clone,
|
||||
U: Printable + Debug,
|
||||
V: Clone + Printable
|
||||
{
|
||||
```
|
||||
|
||||
Generic type parameters and trait bounds are part of Rust's rich type system.
|
||||
Another important kind of generic in Rust interacts with Rust's ownership and
|
||||
references features, and they're called *lifetimes*.
|
480
src/ch10-03-lifetime-syntax.md
Normal file
480
src/ch10-03-lifetime-syntax.md
Normal file
@ -0,0 +1,480 @@
|
||||
## Lifetime Syntax
|
||||
|
||||
Generic type parameters let us abstract over types, and traits let us abstract
|
||||
over behavior. There's one more way that Rust allows us to do something
|
||||
similar: *lifetimes* allow us to be generic over scopes of code.
|
||||
|
||||
Scopes of code? Yes, it's a bit unusual. Lifetimes are, in some ways, Rust's
|
||||
most distinctive feature. They are a bit different than the tools you have used
|
||||
in other programming languages. Lifetimes are a big topic, so we're not going
|
||||
to cover everything about them in this chapter. What we *are* going to do is
|
||||
talk about the very basics of lifetimes, so that when you see the syntax in
|
||||
documentation or other places, you'll be familiar with the concepts. Chapter 20
|
||||
will contain more advanced information about everything lifetimes can do.
|
||||
|
||||
### Core Syntax
|
||||
|
||||
We talked about references in Chapter 4, but we left out an important detail.
|
||||
As it turns out, every reference in Rust has a *lifetime*, which is the scope
|
||||
for which that reference is valid. Most of the time, lifetimes are implicit,
|
||||
but just like we can choose to annotate types everywhere, we can choose to
|
||||
annotate lifetimes.
|
||||
|
||||
Lifetimes have a slightly unusual syntax:
|
||||
|
||||
```rust,ignore
|
||||
&i32 // a reference
|
||||
&'a i32 // a reference with an explicit lifetime
|
||||
```
|
||||
|
||||
The `'a` there is a *lifetime* with the name `a`. A single apostrophe indicates
|
||||
that this name is for a lifetime. Lifetime names need to be declared before
|
||||
they're used. Here's a function signature with lifetime declarations and
|
||||
annotations:
|
||||
|
||||
```rust,ignore
|
||||
fn some_function<'a>(argument: &'a i32) {
|
||||
```
|
||||
|
||||
Notice anything? In the same way that generic type declarations go inside angle
|
||||
brackets after the function name, lifetime declarations also go inside those
|
||||
same angle brackets. We can even write functions that take both a lifetime
|
||||
declaration and a generic type declaration:
|
||||
|
||||
```rust,ignore
|
||||
fn some_function<'a, T>(argument: &'a T) {
|
||||
```
|
||||
|
||||
This function takes one argument, a reference to some type, `T`, and the
|
||||
reference has the lifetime `'a`. In the same way that we parameterize functions
|
||||
that take generic types, we parameterize references with lifetimes.
|
||||
|
||||
So, that's the syntax, but *why*? What does a lifetime do, anyway?
|
||||
|
||||
### Lifetimes Prevent Dangling References
|
||||
|
||||
Consider the program in listing 10-8. There's an outer scope and an inner
|
||||
scope. The outer scope declares a variable named `r` with no initial value, and
|
||||
the inner scope declares a variable named `x` with the initial value of 5.
|
||||
Inside the inner scope, we attempt to set the value of `r` to a reference to
|
||||
`x`. Then the inner scope ends and we attempt to print out the value in `r`:
|
||||
|
||||
```rust,ignore
|
||||
{
|
||||
let r;
|
||||
|
||||
{
|
||||
let x = 5;
|
||||
r = &x;
|
||||
}
|
||||
|
||||
println!("r: {}", r);
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-8: An attempt to use a reference whose value has gone out of scope
|
||||
</caption>
|
||||
|
||||
If we compile this code, we get an error:
|
||||
|
||||
```text
|
||||
error: `x` does not live long enough
|
||||
--> <anon>:6:10
|
||||
|
|
||||
6 | r = &x;
|
||||
| ^ does not live long enough
|
||||
7 | }
|
||||
| - borrowed value only lives until here
|
||||
...
|
||||
10 | }
|
||||
| - borrowed value needs to live until here
|
||||
```
|
||||
|
||||
The variable `x` doesn't "live long enough." Why not? Well, `x` is going to go
|
||||
out of scope when we hit the closing curly brace on line 7, ending the inner
|
||||
scope. But `r` is valid for the outer scope; its scope is larger and we say
|
||||
that it "lives longer." If Rust allowed this code to work, `r` would be
|
||||
referencing memory that was deallocated when `x` went out of scope. That'd be
|
||||
bad! Once it's deallocated, it's meaningless.
|
||||
|
||||
So how does Rust determine that this code should not be allowed? Part of the
|
||||
compiler called the *borrow checker* compares scopes to determine that all
|
||||
borrows are valid. Here's the same example from Listing 10-8 with some
|
||||
annotations:
|
||||
|
||||
```rust,ignore
|
||||
{
|
||||
let r; // -------+-- 'a
|
||||
// |
|
||||
{ // |
|
||||
let x = 5; // -+-----+-- 'b
|
||||
r = &x; // | |
|
||||
} // -+ |
|
||||
// |
|
||||
println!("r: {}", r); // |
|
||||
// |
|
||||
// -------+
|
||||
}
|
||||
```
|
||||
|
||||
Here, we've annotated the lifetime of `r` with `'a` and the lifetime of `x`
|
||||
with `'b`. Rust looks at these lifetimes and sees that `r` has a lifetime of
|
||||
`'a`, but that it refers to something with a lifetime of `'b`. It rejects the
|
||||
program because the lifetime `'b` is shorter than the lifetime of `'a`-- the
|
||||
value that the reference is referring to does not live as long as the reference
|
||||
does.
|
||||
|
||||
Let's look at a different example that compiles because it does not try to make
|
||||
a dangling reference, and see what the lifetimes look like:
|
||||
|
||||
```rust
|
||||
{
|
||||
let x = 5; // -----+-- 'b
|
||||
// |
|
||||
let r = &x; // --+--+-- 'a
|
||||
// | |
|
||||
println!("r: {}", r); // | |
|
||||
// --+ |
|
||||
// -----+
|
||||
}
|
||||
```
|
||||
|
||||
Here, `x` lives for `'b`, which in this case is larger than `'a`. This is
|
||||
allowed: Rust knows that the reference in `r` will always be valid, as it has a
|
||||
smaller scope than `x`, the value it refers to.
|
||||
|
||||
Note that we didn't have to name any lifetimes in the code itself; Rust figured
|
||||
it out for us. One situation in which Rust can't figure out the lifetimes is
|
||||
for a function or method when one of the arguments or return values is a
|
||||
reference, except for a few scenarios we'll discuss in the lifetime elision
|
||||
section.
|
||||
|
||||
### Lifetime Annotations in Struct Definitions
|
||||
|
||||
Another time that Rust can't figure out the lifetimes is when structs have a
|
||||
field that holds a reference. In that case, naming the lifetimes looks like
|
||||
this:
|
||||
|
||||
```rust
|
||||
struct Ref<'a> {
|
||||
x: &'a i32,
|
||||
}
|
||||
```
|
||||
|
||||
Again, the lifetime names are declared in the angle brackets where generic type
|
||||
parameters are declared, and this is because lifetimes are a form of generics.
|
||||
In the examples above, `'a` and `'b` were concrete lifetimes: we knew about `r`
|
||||
and `x` and how long they would live exactly. However, when we write a
|
||||
function, we can't know beforehand exactly all of the arguments that it could
|
||||
be called with and how long they will be valid for. We have to explain to Rust
|
||||
what we expect the lifetime of the argument to be (we'll learn about how
|
||||
to know what you expect the lifetime to be in a bit). This is similar to
|
||||
writing a function that has an argument of a generic type: we don't know what
|
||||
type the arguments will actually end up being when the function gets called.
|
||||
Lifetimes are the same idea, but they are generic over the scope of a
|
||||
reference, rather than a type.
|
||||
|
||||
### Lifetime Annotations in Function Signatures
|
||||
|
||||
Lifetime annotations for functions go on the function signature, but we don't
|
||||
have to annotate any of the code in the function body with lifetimes. That's
|
||||
because Rust can analyze the specific code inside the function without any
|
||||
help. When a function interacts with references that come from or go to code
|
||||
outside that function, however, the lifetimes of those arguments or return
|
||||
values will potentially be different each time that function gets called. Rust
|
||||
would have to analyze every place the function is called to determine that
|
||||
there were no dangling references. That would be impossible because a library
|
||||
that you provide to someone else might be called in code that hasn't been
|
||||
written yet, at the time that you're compiling your library.
|
||||
|
||||
Lifetime parameters specify generic lifetimes that will apply to any specific
|
||||
lifetimes the function gets called with. The annotation of lifetime parameters
|
||||
tell Rust what it needs to know in order to be able to analyze a function
|
||||
without knowing about all possible calling code. Lifetime annotations do not
|
||||
change how long any of the references involved live. In the same way that
|
||||
functions can accept any type when the signature specifies a generic type
|
||||
parameter, functions can accept references with any lifetime when the signature
|
||||
specifies a generic lifetime parameter.
|
||||
|
||||
To understand lifetime annotations in context, let's write a function that will
|
||||
return the longest of two string slices. The way we want to be able to call
|
||||
this function is by passing two string slices, and we want to get back a string
|
||||
slice. The code in Listing 10-9 should print `The longest string is abcd` once
|
||||
we've implemented the `longest` function:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
# fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
# if x.len() > y.len() {
|
||||
# x
|
||||
# } else {
|
||||
# y
|
||||
# }
|
||||
# }
|
||||
#
|
||||
fn main() {
|
||||
let a = String::from("abcd");
|
||||
let b = "xyz";
|
||||
|
||||
let c = longest(a.as_str(), b);
|
||||
println!("The longest string is {}", c);
|
||||
}
|
||||
```
|
||||
|
||||
<caption>
|
||||
Listing 10-9: A `main` function that demonstrates how we'd like to use the
|
||||
`longest` function
|
||||
</caption>
|
||||
|
||||
Note that we want the function to take string slices because we don't want the
|
||||
`longest` function to take ownership of its arguments, and we want the function
|
||||
to be able to accept slices of a `String` (like `a`) is as well as string
|
||||
literals (`b`). Refer back to the "String Slices as Arguments" section of
|
||||
Chapter 4 for more discussion about why these are the arguments we want.
|
||||
|
||||
Here's the start of an implementation of the `longest` function that won't
|
||||
compile yet:
|
||||
|
||||
```rust,ignore
|
||||
fn longest(x: &str, y: &str) -> &str {
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we try to compile this, we get an error that talks about lifetimes:
|
||||
|
||||
```text
|
||||
error[E0106]: missing lifetime specifier
|
||||
|
|
||||
1 | fn longest(x: &str, y: &str) -> &str {
|
||||
| ^ expected lifetime parameter
|
||||
|
|
||||
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
|
||||
```
|
||||
|
||||
The help text is telling us that the return type needs a generic lifetime
|
||||
parameter on it because this function is returning a reference and Rust can't
|
||||
tell if the reference being returned refers to `x` or `y`. Actually, we don't
|
||||
know either, since in the `if` block in the body of this function returns a
|
||||
reference to `x` and the `else` block returns a reference to `y`! The way to
|
||||
specify the lifetime parameters in this case is to have the same lifetime for
|
||||
all of the input parameters and the return type:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will compile and will produce the result we want with the `main` function
|
||||
in Listing 10-9. This function signature is now saying that for some lifetime
|
||||
named `'a`, it will get two arguments, both which are string slices that live
|
||||
at least as long as the lifetime `'a`. The function will return a string slice
|
||||
that also will last at least as long as the lifetime `'a`. This is the contract
|
||||
we are telling Rust we want it to enforce. By specifying the lifetime
|
||||
parameters in this function signature, we are not changing the lifetimes of any
|
||||
values passed in or returned, but we are saying that any values that do not
|
||||
adhere to this contract should be rejected by the borrow checker. This function
|
||||
does not know (or need to know) exactly how long `x` and `y` will live since it
|
||||
knows that there is some scope that can be substituted for `'a` that will
|
||||
satisfy this signature.
|
||||
|
||||
The exact way to specify lifetime parameters depends on what your function is
|
||||
doing. If the function didn't actually return the longest string slice but
|
||||
instead always returned the first argument, we wouldn't need to specify a
|
||||
lifetime on `y`. This code compiles:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust
|
||||
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
|
||||
x
|
||||
}
|
||||
```
|
||||
|
||||
The lifetime parameter for the return type needs to be specified and needs to
|
||||
match one of the arguments' lifetime parameters. If the reference returned does
|
||||
*not* refer to one of the arguments, the only other possibility is that it
|
||||
refers to a value created within this function, and that would be a dangling
|
||||
reference since the value will go out of scope at the end of the function.
|
||||
Consider this attempted implementation of `longest`:
|
||||
|
||||
Filename: src/main.rs
|
||||
|
||||
```rust,ignore
|
||||
fn longest<'a>(x: &str, y: &str) -> &'a str {
|
||||
let result = String::from("really long string");
|
||||
result.as_str()
|
||||
}
|
||||
```
|
||||
|
||||
Even though we've specified a lifetime for the return type, this function fails
|
||||
to compile with the following error message:
|
||||
|
||||
```text
|
||||
error: `result` does not live long enough
|
||||
|
|
||||
3 | result.as_str()
|
||||
| ^^^^^^ does not live long enough
|
||||
4 | }
|
||||
| - borrowed value only lives until here
|
||||
|
|
||||
note: borrowed value must be valid for the lifetime 'a as defined on the block at 1:44...
|
||||
|
|
||||
1 | fn longest<'a>(x: &str, y: &str) -> &'a str {
|
||||
| ^
|
||||
```
|
||||
|
||||
The problem is that `result` will go out of scope and get cleaned up at the end
|
||||
of the `longest` function, and we're trying to return a reference to `result`
|
||||
from the function. There's no way we can specify lifetime parameters that would
|
||||
change the dangling reference, and Rust won't let us create a dangling
|
||||
reference. In this case, the best fix would be to return an owned data type
|
||||
rather than a reference so that the calling function is then responsible for
|
||||
cleaning up the value.
|
||||
|
||||
Ultimately, lifetime syntax is about connecting the lifetimes of various
|
||||
arguments and return values of functions. Once they're connected, Rust has
|
||||
enough information to allow memory-safe operations and disallow operations that
|
||||
would create dangling pointers or otherwise violate memory safety.
|
||||
|
||||
### Lifetime Elision
|
||||
|
||||
If every reference has a lifetime, and we need to provide them for functions
|
||||
that use references as arguments or return values, then why did this function
|
||||
from the "String Slices" section of Chapter 4 compile? We haven't annotated any
|
||||
lifetimes here, yet Rust happily compiles this function:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```rust
|
||||
fn first_word(s: &str) -> &str {
|
||||
let bytes = s.as_bytes();
|
||||
|
||||
for (i, &item) in bytes.iter().enumerate() {
|
||||
if item == b' ' {
|
||||
return &s[0..i];
|
||||
}
|
||||
}
|
||||
|
||||
&s[..]
|
||||
}
|
||||
```
|
||||
|
||||
The answer is historical: in early versions of pre-1.0 Rust, this would not
|
||||
have compiled. Every reference needed an explicit lifetime. At that time, the
|
||||
function signature would have been written like this:
|
||||
|
||||
```rust,ignore
|
||||
fn first_word<'a>(s: &'a str) -> &'a str {
|
||||
```
|
||||
|
||||
After writing a lot of Rust code, some patterns developed. The Rust team
|
||||
noticed that the vast majority of code followed the pattern, and being forced
|
||||
to use explicit lifetime syntax on every reference wasn't a very great
|
||||
developer experience.
|
||||
|
||||
To make it so that lifetime annotations weren't needed as often, they added
|
||||
*lifetime elision rules* to Rust's analysis of references. This feature isn't
|
||||
full inference: Rust doesn't try to guess what you meant in places where there
|
||||
could be ambiguity. The rules are a very basic set of particular cases, and if
|
||||
your code fits one of those cases, you don't need to write the lifetimes
|
||||
explicitly. Here are the rules:
|
||||
|
||||
Lifetimes on function arguments are called *input lifetimes*, and lifetimes on
|
||||
return values are called *output lifetimes*. There's one rule related to how
|
||||
Rust infers input lifetimes in the absence of explicit annotations:
|
||||
|
||||
1. Each argument that is a reference and therefore needs a lifetime parameter
|
||||
gets its own. In other words, a function with one argument gets one lifetime
|
||||
parameter: `fn foo<'a>(x: &'a i32)`, a function with two arguments gets two
|
||||
separate lifetime parameters: `fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`, and
|
||||
so on.
|
||||
|
||||
And two rules related to output lifetimes:
|
||||
|
||||
2. If there is exactly one input lifetime parameter, that lifetime is assigned
|
||||
to all output lifetime parameters: `fn foo<'a>(x: &'a i32) -> &'a i32`.
|
||||
3. If there are multiple input lifetime parameters, but one of them is `&self`
|
||||
or `&mut self`, then the lifetime of `self` is the lifetime assigned to all
|
||||
output lifetime parameters. This makes writing methods much nicer.
|
||||
|
||||
If none of these three rules apply, then you must explicitly annotate input and
|
||||
output lifetimes. These rules do apply in the `first_word` function, which is
|
||||
why we didn't have to specify any lifetimes.
|
||||
|
||||
These rules cover the vast majority of cases, allowing you to write a lot of
|
||||
code without needing to specify explicit lifetimes. However, Rust is always
|
||||
checking these rules and the lifetimes in your program, and cases in which the
|
||||
lifetime elision rules do not apply are cases where you'll need to add lifetime
|
||||
parameters to help Rust understand the contracts of your code.
|
||||
|
||||
### Lifetime Annotations in Method Definitions
|
||||
|
||||
Now that we've gone over the lifetime elision rules, defining methods on
|
||||
structs that hold references will make more sense. The lifetime name needs to
|
||||
be declared after the `impl` keyword and then used after the struct's name,
|
||||
since the lifetime is part of the struct's type. The lifetimes can be elided in
|
||||
any methods where the output type's lifetime is the same as that of the
|
||||
struct's because of the third elision rule. Here's a struct called `App` that
|
||||
holds a reference to another struct, `Config`, defined elsewhere. The
|
||||
`append_to_name` method does not need lifetime annotations even though the
|
||||
method has a reference as an argument and is returning a reference; the
|
||||
lifetime of the return value will be the lifetime of `self`:
|
||||
|
||||
Filename: src/lib.rs
|
||||
|
||||
```rust
|
||||
# struct Config {}
|
||||
#
|
||||
struct App<'a> {
|
||||
name: String,
|
||||
config: &'a Config,
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
fn append_to_name(&mut self, suffix: &str) -> &str {
|
||||
self.name.push_str(suffix);
|
||||
self.name.as_str()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### The Static Lifetime
|
||||
|
||||
There is *one* special lifetime that Rust knows about: `'static`. The `'static`
|
||||
lifetime is the entire duration of the program. All string literals have the
|
||||
`'static` lifetime:
|
||||
|
||||
```rust
|
||||
let s: &'static str = "I have a static lifetime.";
|
||||
```
|
||||
|
||||
The text of this string is stored directly in the binary of your program and
|
||||
the binary of your program is always available. Therefore, the lifetime of all
|
||||
string literals is `'static`. You may see suggestions to use the `'static`
|
||||
lifetime in error message help text, but before adding it, think about whether
|
||||
the reference you have is one that actually lives the entire lifetime of your
|
||||
program or not (or even if you want it to live that long, if it could). Most of
|
||||
the time, the problem in the code is an attempt to create a dangling reference
|
||||
or a mismatch of the available lifetimes, and the solution is fixing those
|
||||
problems, not specifying the `'static` lifetime.
|
||||
|
||||
## Summary
|
||||
|
||||
We've covered the basics of Rust's system of generics. Generics are the core to
|
||||
building good abstractions, and can be used in a number of ways. There's more
|
||||
to learn about them, particularly lifetimes, but we'll cover those in later
|
||||
chapters. Let's move on to I/O functionality.
|
@ -1,192 +0,0 @@
|
||||
# Generics
|
||||
|
||||
We've been working with a `Point` struct that looks like this:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
```
|
||||
|
||||
But what if we didn't want to always use an `f64` here? What about an `f32` for
|
||||
when we need less precision? Or an `i32` if we only want integer coordinates?
|
||||
|
||||
While our simple `Point` struct may be a bit too simple to bother making
|
||||
generic in a real application, we're going to stick with it to show you the
|
||||
syntax. Especially when building library code, generics allow for more code
|
||||
re-use, and unlock a lot of powerful techniques.
|
||||
|
||||
## Generic data types
|
||||
|
||||
'Generics' let us write code that allows for several different types, while
|
||||
letting us have one definition. A more generic `Point` would look like this:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
```
|
||||
|
||||
There are two changes here, and they both involve this new `T`. The first change
|
||||
is in the definition:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
struct Point<T> {
|
||||
# x: T,
|
||||
# y: T,
|
||||
# }
|
||||
```
|
||||
|
||||
Our previous definition said, "We are defining a struct named Point." This
|
||||
definition says something slightly different: "We are defining a struct named
|
||||
Point with one type parameter `T`."
|
||||
|
||||
Let's talk about this term *type parameter*. We've already seen one other thing
|
||||
called a "parameter" in Rust: function parameters:
|
||||
|
||||
```rust
|
||||
fn plus_one(x: i32) -> i32 {
|
||||
x + 1
|
||||
}
|
||||
```
|
||||
|
||||
Here, `x` is a parameter to this function. We can call this function with a
|
||||
different value, and `x` will change each time it's called:
|
||||
|
||||
```rust
|
||||
# fn plus_one(x: i32) -> i32 {
|
||||
# x + 1
|
||||
# }
|
||||
let six = plus_one(5);
|
||||
let eleven = plus_one(10);
|
||||
```
|
||||
|
||||
In the same way, a type parameter allows us to define a data type which can be
|
||||
different each time we use it:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
let integral_point = Point { x: 5, y: 5 };
|
||||
let floating_point = Point { x: 5.0, y: 5.0 };
|
||||
```
|
||||
|
||||
Here, `integral_point` uses `i32` values for `T`, and `floating_point` uses
|
||||
`f64` values. This also leads us to talk about the second change we made to `Point`:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug,Copy,Clone)]
|
||||
# struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
# }
|
||||
```
|
||||
|
||||
Instead of saying `x: i32`, we say `x: T`. This `T` is the same one that we
|
||||
used above in the struct declaration. Because `x` and `y` both use `T`, they'll
|
||||
be the same type. We could give them different types:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point<T, OtherT> {
|
||||
x: T,
|
||||
y: OtherT,
|
||||
}
|
||||
|
||||
let different = Point { x: 5, y: 5.0 };
|
||||
let same = Point { x: 5.0, y: 5.0 };
|
||||
```
|
||||
|
||||
Here, instead of a single parameter, `T`, we have two: `T` and `OtherT`. Type
|
||||
parameters have the same naming convention as other types: `CamelCase`.
|
||||
However, you'll often see short, one-letter names used for types. `T` is very
|
||||
common, because it's short for "type", but you can name them something longer
|
||||
if you'd like. In this version of `Point`, we say that `x` has the type `T`,
|
||||
and `y` has the type `OtherT`. This lets us give them two different types, but
|
||||
they don't have to be.
|
||||
|
||||
## Generic functions
|
||||
|
||||
Regular old functions can also take generic parameters, with a syntax that looks
|
||||
very similar:
|
||||
|
||||
```rust
|
||||
fn foo<T>(x: T) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This `foo` function has one generic parameter, `T`, and takes one argument,
|
||||
`x`, which has the type `T`. Let's talk a little bit more about what this means.
|
||||
|
||||
|
||||
## Generic methods
|
||||
|
||||
We've seen how to define methods with the `impl` keyword. Our generic `Point`
|
||||
can have generic methods, too:
|
||||
|
||||
```rust
|
||||
#[derive(Debug,Copy,Clone)]
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T> Point<T> {
|
||||
fn some_method(&self) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We also need the `<T>` after `impl`. This line reads, "We will be implementing
|
||||
methods with one generic type parameter, `T`, for a type, `Point`, which takes
|
||||
one generic type `T`." In a sense, the `impl<T>` says "we will be using a type
|
||||
`T`" and the `Point<T>` says "that `T` is used for `Point`." In this simple
|
||||
case, this syntax can feel a bit redundant, but when we get into some of Rust's
|
||||
more advanced features later, this distinction will become more useful.
|
||||
|
||||
## There's more to the story
|
||||
|
||||
This section covered the basic syntax of generics, but it's not the full story.
|
||||
For example, let's try to implement our `foo` function: we'll have it print out
|
||||
the value of `x`:
|
||||
|
||||
```rust,ignore
|
||||
fn foo<T>(x: T) {
|
||||
println!("x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
We'll get an error:
|
||||
|
||||
```bash
|
||||
error: the trait `core::fmt::Display` is not implemented for the type `T` [E0277]
|
||||
println!("x is: {}", x);
|
||||
^
|
||||
```
|
||||
|
||||
We can't print out `x`! The error messages reference something we talked about
|
||||
briefly before, the `Display` trait. In order to implement this function, we
|
||||
need to talk about traits. But we only need to talk about traits to implement
|
||||
our own generic functions; we don't need this understanding to use them. So
|
||||
rather than get into more details about this right now, let's talk about other
|
||||
useful Rust data types, and we can come back to implementing generic functions
|
||||
in the chapter about traits.
|
||||
|
||||
For now, the important bits to understand:
|
||||
|
||||
* Generic type parameters are kind of like function parameters, but for types
|
||||
instead of values.
|
||||
* Type parameters go inside `<>`s and are usually named things like `T`.
|
||||
|
||||
With that, let's talk about another fundamental Rust data type: enums.
|
Loading…
Reference in New Issue
Block a user