mirror of
https://github.com/rust-lang/book.git
synced 2026-05-18 05:32:16 -04:00
Changes made during 2nd page review of chapter 19
This commit is contained in:
@@ -58,7 +58,7 @@ All iterators implement a trait named `Iterator` that is defined in the
|
||||
standard library. The definition of the trait looks like this:
|
||||
|
||||
```rust
|
||||
trait Iterator {
|
||||
pub trait Iterator {
|
||||
type Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
|
||||
@@ -10,13 +10,13 @@ make sure you have a grasp of all the features Rust has to offer.
|
||||
|
||||
In this chapter, we’ll cover:
|
||||
|
||||
* Unsafe Rust: How to opt out of some of Rust’s guarantees and take
|
||||
* Unsafe Rust: how to opt out of some of Rust’s guarantees and take
|
||||
responsibility for manually upholding those guarantees
|
||||
* Advanced lifetimes: Syntax for complex lifetime situations
|
||||
* Advanced traits: Associated types, default type parameters, fully qualified
|
||||
* Advanced lifetimes: syntax for complex lifetime situations
|
||||
* Advanced traits: associated types, default type parameters, fully qualified
|
||||
syntax, supertraits, and the newtype pattern in relation to traits
|
||||
* Advanced types: More about the newtype pattern, type aliases, the *never*
|
||||
type, and dynamically sized types
|
||||
* Advanced functions and closures: Function pointers and returning closures
|
||||
* Advanced types: more about the newtype pattern, type aliases, the never type,
|
||||
and dynamically sized types
|
||||
* Advanced functions and closures: function pointers and returning closures
|
||||
|
||||
It’s a panoply of Rust features with something for everyone! Let’s dive in!
|
||||
|
||||
@@ -7,27 +7,27 @@ and works just like regular Rust, but gives us extra superpowers.
|
||||
|
||||
Unsafe Rust exists because, by nature, static analysis is conservative. When
|
||||
the compiler tries to determine whether or not code upholds the guarantees,
|
||||
it’s better for it to reject some valid programs rather than accepting some
|
||||
it’s better for it to reject some valid programs rather than accept some
|
||||
invalid programs. Although the code might be okay, as far as Rust is able to
|
||||
tell, it’s not! In these cases, we can use unsafe code to tell the compiler,
|
||||
“trust me, I know what I’m doing.” The downside is that we use it at our own
|
||||
risk: if we use unsafe code incorrectly, problems due to memory unsafety, such
|
||||
tell, it’s not! In these cases, you can use unsafe code to tell the compiler,
|
||||
“Trust me, I know what I’m doing.” The downside is that you use it at your own
|
||||
risk: if you use unsafe code incorrectly, problems due to memory unsafety, such
|
||||
as null pointer dereferencing, can occur.
|
||||
|
||||
Another reason Rust has an unsafe alter ego is that the underlying computer
|
||||
hardware is inherently unsafe. If Rust didn’t let us do unsafe operations, we
|
||||
couldn’t do certain tasks. Rust needs to allow us to do low-level systems
|
||||
hardware is inherently unsafe. If Rust didn’t let you do unsafe operations, you
|
||||
couldn’t do certain tasks. Rust needs to allow you to do low-level systems
|
||||
programming, such as directly interacting with the operating system or even
|
||||
writing our own operating system. Working with low-level systems programming is
|
||||
one of the goals of the language. Let’s explore what we can do with unsafe Rust
|
||||
and how to do it.
|
||||
writing your own operating system. Working with low-level systems programming
|
||||
is one of the goals of the language. Let’s explore what we can do with unsafe
|
||||
Rust and how to do it.
|
||||
|
||||
### Unsafe Superpowers
|
||||
|
||||
To switch to unsafe Rust, we use the `unsafe` keyword, and then start a new
|
||||
block that holds the unsafe code. We can take four actions in unsafe Rust,
|
||||
which we call *unsafe superpowers*, that we can’t in safe Rust. Those
|
||||
superpowers include the ability to:
|
||||
To switch to unsafe Rust, use the `unsafe` keyword and then start a new block
|
||||
that holds the unsafe code. You can take four actions in unsafe Rust, called
|
||||
*unsafe superpowers*, that you can’t in safe Rust. Those superpowers include
|
||||
the ability to:
|
||||
|
||||
* Dereference a raw pointer
|
||||
* Call an unsafe function or method
|
||||
@@ -36,17 +36,17 @@ superpowers include the ability to:
|
||||
|
||||
It’s important to understand that `unsafe` doesn’t turn off the borrow checker
|
||||
or disable any other of Rust’s safety checks: if you use a reference in unsafe
|
||||
code, it will still be checked. The `unsafe` keyword only gives us access to
|
||||
code, it will still be checked. The `unsafe` keyword only gives you access to
|
||||
these four features that are then not checked by the compiler for memory
|
||||
safety. We still get some degree of safety inside of an unsafe block.
|
||||
safety. You’ll still get some degree of safety inside of an unsafe block.
|
||||
|
||||
In addition, `unsafe` does not mean the code inside the block is necessarily
|
||||
dangerous or that it will definitely have memory safety problems: the intent is
|
||||
that as the programmer, we’ll ensure the code inside an `unsafe` block will
|
||||
that as the programmer, you’ll ensure the code inside an `unsafe` block will
|
||||
access memory in a valid way.
|
||||
|
||||
People are fallible, and mistakes will happen, but by requiring these four
|
||||
unsafe operations to be inside blocks annotated with `unsafe` we’ll know that
|
||||
unsafe operations to be inside blocks annotated with `unsafe` you’ll know that
|
||||
any errors related to memory safety must be within an `unsafe` block. Keep
|
||||
`unsafe` blocks small; you’ll be thankful later when you investigate memory
|
||||
bugs.
|
||||
@@ -60,7 +60,7 @@ from leaking out into all the places that you or your users might want to use
|
||||
the functionality implemented with `unsafe` code, because using a safe
|
||||
abstraction is safe.
|
||||
|
||||
Let’s look at each of the four unsafe superpowers in turn: we’ll also look at
|
||||
Let’s look at each of the four unsafe superpowers in turn. We’ll also look at
|
||||
some abstractions that provide a safe interface to unsafe code.
|
||||
|
||||
### Dereferencing a Raw Pointer
|
||||
@@ -70,10 +70,10 @@ compiler ensures references are always valid. Unsafe Rust has two new types
|
||||
called *raw pointers* that are similar to references. As with references, raw
|
||||
pointers can be immutable or mutable and are written as `*const T` and `*mut
|
||||
T`, respectively. The asterisk isn’t the dereference operator; it’s part of the
|
||||
type name. In the context of raw pointers, “immutable” means that the pointer
|
||||
type name. In the context of raw pointers, *immutable* means that the pointer
|
||||
can’t be directly assigned to after being dereferenced.
|
||||
|
||||
Different from references and smart pointers, keep in mind that raw pointers:
|
||||
Different from references and smart pointers, raw pointers:
|
||||
|
||||
* Are allowed to ignore the borrowing rules by having both immutable and
|
||||
mutable pointers or multiple mutable pointers to the same location
|
||||
@@ -81,8 +81,8 @@ Different from references and smart pointers, keep in mind that raw pointers:
|
||||
* Are allowed to be null
|
||||
* Don’t implement any automatic cleanup
|
||||
|
||||
By opting out of having Rust enforce these guarantees, we can make the
|
||||
trade-off of giving up guaranteed safety to gain performance or the ability to
|
||||
By opting out of having Rust enforce these guarantees, you can give up
|
||||
guaranteed safety in exchange for greater performance or the ability to
|
||||
interface with another language or hardware where Rust’s guarantees don’t apply.
|
||||
|
||||
Listing 19-1 shows how to create an immutable and a mutable raw pointer from
|
||||
@@ -112,7 +112,7 @@ Listing 19-2 shows how to create a raw pointer to an arbitrary location in
|
||||
memory. Trying to use arbitrary memory is undefined: there might be data at
|
||||
that address or there might not, the compiler might optimize the code so there
|
||||
is no memory access, or the program might error with a segmentation fault.
|
||||
Usually, there is no good reason to write code like this, but it is possible:
|
||||
Usually, there is no good reason to write code like this, but it is possible.
|
||||
|
||||
```rust
|
||||
let address = 0x012345usize;
|
||||
@@ -144,16 +144,16 @@ unsafe {
|
||||
Creating a pointer does no harm; it’s only when we try to access the value that
|
||||
it points at that we might end up dealing with an invalid value.
|
||||
|
||||
Note also that in Listing 19-1 and 19-3 we created `*const i32` and `*mut i32`
|
||||
Note also that in Listing 19-1 and 19-3, we created `*const i32` and `*mut i32`
|
||||
raw pointers that both pointed to the same memory location, where `num` is
|
||||
stored. If we instead tried to create an immutable and a mutable reference to
|
||||
`num`, the code would not have compiled because Rust’s ownership rules don’t
|
||||
allow a mutable reference at the same time as any immutable references. With
|
||||
raw pointers, we can create a mutable pointer and an immutable pointer to the
|
||||
same location, and change data through the mutable pointer, potentially
|
||||
creating a data race. Be careful!
|
||||
same location and change data through the mutable pointer, potentially creating
|
||||
a data race. Be careful!
|
||||
|
||||
With all of these dangers, why would we ever use raw pointers? One major use
|
||||
With all of these dangers, why would you ever use raw pointers? One major use
|
||||
case is when interfacing with C code, as you’ll see in the next section,
|
||||
“Calling an Unsafe Function or Method.” Another case is when building up safe
|
||||
abstractions that the borrow checker doesn’t understand. We’ll introduce unsafe
|
||||
@@ -245,17 +245,17 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
||||
<span class="caption">Listing 19-5: An attempted implementation of
|
||||
`split_at_mut` using only safe Rust</span>
|
||||
|
||||
This function first gets the total length of the slice, then it asserts that
|
||||
the index given as a parameter is within the slice by checking that it’s less
|
||||
than or equal to the length. The assertion means that if we pass an index that
|
||||
is greater than the index to split the slice at, the function will panic before
|
||||
it attempts to use that index.
|
||||
This function first gets the total length of the slice. Then it asserts that
|
||||
the index given as a parameter is within the slice by checking whether it’s
|
||||
less than or equal to the length. The assertion means that if we pass an index
|
||||
that is greater than the index to split the slice at, the function will panic
|
||||
before it attempts to use that index.
|
||||
|
||||
Then we return two mutable slices in a tuple: one from the start of the
|
||||
original slice to the `mid` index and another from `mid` to the end of the
|
||||
slice.
|
||||
|
||||
When we try to compile the code in Listing 19-5, we’ll get an error:
|
||||
When we try to compile the code in Listing 19-5, we’ll get an error.
|
||||
|
||||
```text
|
||||
error[E0499]: cannot borrow `*slice` as mutable more than once at a time
|
||||
@@ -306,11 +306,11 @@ in the variable `ptr`.
|
||||
|
||||
We keep the assertion that the `mid` index is within the slice. Then we get to
|
||||
the unsafe code: the `slice::from_raw_parts_mut` function takes a raw pointer
|
||||
and a length, and creates a slice. We use this function to create a slice that
|
||||
starts from `ptr` and is `mid` items long. Then we call the `offset` method on
|
||||
`ptr` with `mid` as an argument to get a raw pointer that starts at `mid`, and
|
||||
we create a slice using that pointer and the remaining number of items after
|
||||
`mid` as the length.
|
||||
and a length, and it creates a slice. We use this function to create a slice
|
||||
that starts from `ptr` and is `mid` items long. Then we call the `offset`
|
||||
method on `ptr` with `mid` as an argument to get a raw pointer that starts at
|
||||
`mid`, and we create a slice using that pointer and the remaining number of
|
||||
items after `mid` as the length.
|
||||
|
||||
The function `slice::from_raw_parts_mut` is unsafe because it takes a raw
|
||||
pointer and must trust that this pointer is valid. The `offset` method on raw
|
||||
@@ -330,7 +330,7 @@ data this function has access to.
|
||||
|
||||
In contrast, the use of `slice::from_raw_parts_mut` in Listing 19-7 would
|
||||
likely crash when the slice is used. This code takes an arbitrary memory
|
||||
location and creates a slice ten thousand items long:
|
||||
location and creates a slice 10,000 items long.
|
||||
|
||||
```rust
|
||||
use std::slice;
|
||||
@@ -485,16 +485,16 @@ races.
|
||||
With mutable data that is globally accessible, it’s difficult to ensure there
|
||||
are no data races, which is why Rust considers mutable static variables to be
|
||||
unsafe. Where possible, it’s preferable to use the concurrency techniques and
|
||||
thread-safe smart pointers we discussed in Chapter 16, so the compiler checks
|
||||
thread-safe smart pointers we discussed in Chapter 16 so the compiler checks
|
||||
that data accessed from different threads is done safely.
|
||||
|
||||
### Implementing an Unsafe Trait
|
||||
|
||||
The final action that only works with `unsafe` is implementing an unsafe trait.
|
||||
The final action that works only with `unsafe` is implementing an unsafe trait.
|
||||
A trait is unsafe when at least one of its methods has some invariant that the
|
||||
compiler can’t verify. We can declare that a trait is `unsafe` by adding the
|
||||
`unsafe` keyword before `trait`; then implementation of the trait must be
|
||||
marked as `unsafe` too, as shown in Listing 19-11.
|
||||
`unsafe` keyword before `trait` and marking the implementation of the trait as
|
||||
`unsafe` too, as shown in Listing 19-11.
|
||||
|
||||
```rust
|
||||
unsafe trait Foo {
|
||||
|
||||
@@ -6,12 +6,12 @@ lifetimes of different references relate. You saw how every reference has a
|
||||
lifetime, but most of the time, Rust will let you elide lifetimes. Now we’ll
|
||||
look at three advanced features of lifetimes that we haven’t covered yet:
|
||||
|
||||
* Lifetime subtyping: Ensures that one lifetime outlives another lifetime
|
||||
* Lifetime bounds: Specifies a lifetime for a reference to a generic type
|
||||
* Inference of trait object lifetimes: How the compiler infers trait object
|
||||
lifetimes and when they need to be specified
|
||||
* Lifetime subtyping: ensures that one lifetime outlives another lifetime
|
||||
* Lifetime bounds: specifies a lifetime for a reference to a generic type
|
||||
* Inference of trait object lifetimes: allows the compiler to infer trait
|
||||
object lifetimes and when they need to be specified
|
||||
|
||||
### Lifetime Subtyping Ensures One Lifetime Outlives Another
|
||||
### Ensuring One Lifetime Outlives Another with Lifetime Subtyping
|
||||
|
||||
*Lifetime subtyping* specifies that one lifetime should outlive another
|
||||
lifetime. To explore lifetime subtyping, imagine we want to write a parser.
|
||||
@@ -44,7 +44,7 @@ Compiling the code results in errors because Rust expects lifetime parameters
|
||||
on the string slice in `Context` and the reference to a `Context` in `Parser`.
|
||||
|
||||
For simplicity’s sake, the `parse` function returns `Result<(), &str>`. That
|
||||
is, the function will do nothing on success, and on failure will return the
|
||||
is, the function will do nothing on success and, on failure, will return the
|
||||
part of the string slice that didn’t parse correctly. A real implementation
|
||||
would provide more error information and would return a structured data type
|
||||
when parsing succeeds. We won’t be discussing those details because they aren’t
|
||||
@@ -88,14 +88,14 @@ impl<'a> Parser<'a> {
|
||||
`Parser` with lifetime parameters</span>
|
||||
|
||||
This code compiles just fine. It tells Rust that a `Parser` holds a reference
|
||||
to a `Context` with lifetime `'a`, and that `Context` holds a string slice that
|
||||
to a `Context` with lifetime `'a` and that `Context` holds a string slice that
|
||||
also lives as long as the reference to the `Context` in `Parser`. Rust’s
|
||||
compiler error message stated that lifetime parameters were required for these
|
||||
references, and we’ve now added lifetime parameters.
|
||||
|
||||
Next, in Listing 19-14, we’ll add a function that takes an instance of
|
||||
`Context`, uses a `Parser` to parse that context, and returns what `parse`
|
||||
returns. This code doesn’t quite work:
|
||||
returns. This code doesn’t quite work.
|
||||
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
@@ -191,18 +191,18 @@ string slice that `Context` holds is the same as that of the lifetime of the
|
||||
reference to `Context` that `Parser` holds.
|
||||
|
||||
The `parse_context` function can’t see that within the `parse` function, the
|
||||
string slice returned will outlive `Context` and `Parser`, and that the
|
||||
string slice returned will outlive `Context` and `Parser` and that the
|
||||
reference `parse_context` returns refers to the string slice, not to `Context`
|
||||
or `Parser`.
|
||||
|
||||
By knowing what the implementation of `parse` does, we know that the only
|
||||
reason the return value of `parse` is tied to the `Parser` is because it’s
|
||||
referencing the `Parser`’s `Context`, which is referencing the string slice.
|
||||
So, it’s really the lifetime of the string slice that `parse_context` needs to
|
||||
care about. We need a way to tell Rust that the string slice in `Context` and
|
||||
the reference to the `Context` in `Parser` have different lifetimes and that
|
||||
the return value of `parse_context` is tied to the lifetime of the string slice
|
||||
in `Context`.
|
||||
reason the return value of `parse` is tied to the `Parser` instance is that
|
||||
it’s referencing the `Parser` instance’s `Context`, which is referencing the
|
||||
string slice. So, it’s really the lifetime of the string slice that
|
||||
`parse_context` needs to care about. We need a way to tell Rust that the string
|
||||
slice in `Context` and the reference to the `Context` in `Parser` have
|
||||
different lifetimes and that the return value of `parse_context` is tied to the
|
||||
lifetime of the string slice in `Context`.
|
||||
|
||||
First, we’ll try giving `Parser` and `Context` different lifetime parameters,
|
||||
as shown in Listing 19-15. We’ll use `'s` and `'c` as lifetime parameter names
|
||||
@@ -298,7 +298,7 @@ lifetime of the string slice is longer than the reference to the `Context`.
|
||||
That was a very long-winded example, but as we mentioned at the start of this
|
||||
chapter, Rust’s advanced features are very specific. You won’t often need the
|
||||
syntax we described in this example, but in such situations, you’ll know how to
|
||||
refer to something you have a reference to.
|
||||
refer to something and give it the necessary lifetime.
|
||||
|
||||
### Lifetime Bounds on References to Generic Types
|
||||
|
||||
@@ -312,7 +312,7 @@ As an example, consider a type that is a wrapper over references. Recall the
|
||||
section in Chapter 15: its `borrow` and `borrow_mut` methods return the types
|
||||
`Ref` and `RefMut`, respectively. These types are wrappers over references that
|
||||
keep track of the borrowing rules at runtime. The definition of the `Ref`
|
||||
struct is shown in Listing 19-16, without lifetime bounds for now:
|
||||
struct is shown in Listing 19-16, without lifetime bounds for now.
|
||||
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
@@ -321,7 +321,7 @@ struct Ref<'a, T>(&'a T);
|
||||
```
|
||||
|
||||
<span class="caption">Listing 19-16: Defining a struct to wrap a reference to a
|
||||
generic type, without lifetime bounds to start</span>
|
||||
generic type, without lifetime bounds</span>
|
||||
|
||||
Without explicitly constraining the lifetime `'a` in relation to the generic
|
||||
parameter `T`, Rust will error because it doesn’t know how long the generic
|
||||
@@ -355,7 +355,7 @@ consider adding an explicit lifetime bound `T: 'a` so that the reference type
|
||||
```
|
||||
|
||||
Listing 19-17 shows how to apply this advice by specifying the lifetime bound
|
||||
when we declare the generic type `T`:
|
||||
when we declare the generic type `T`.
|
||||
|
||||
```rust
|
||||
struct Ref<'a, T: 'a>(&'a T);
|
||||
@@ -398,7 +398,7 @@ happens if the type implementing the trait in the trait object has a lifetime
|
||||
of its own. Consider Listing 19-19 where we have a trait `Red` and a struct
|
||||
`Ball`. The `Ball` struct holds a reference (and thus has a lifetime parameter)
|
||||
and also implements trait `Red`. We want to use an instance of `Ball` as the
|
||||
trait object `Box<Red>`:
|
||||
trait object `Box<Red>`.
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
@@ -430,7 +430,7 @@ rules for working with lifetimes and trait objects:
|
||||
is `'a`.
|
||||
* With a single `T: 'a` clause, the default lifetime of the trait object is
|
||||
`'a`.
|
||||
* With multiple `T: 'a`-like clauses, there is no default lifetime; we must be
|
||||
* With multiple clauses like `T: 'a`, there is no default lifetime; we must be
|
||||
explicit.
|
||||
|
||||
When we must be explicit, we can add a lifetime bound on a trait object like
|
||||
|
||||
@@ -4,7 +4,7 @@ We first covered traits in the “Traits: Defining Shared Behavior” section of
|
||||
Chapter 10, but as with lifetimes, we didn’t discuss the more advanced details.
|
||||
Now that you know more about Rust, we can get into the nitty-gritty.
|
||||
|
||||
### Associated Types Specify Placeholder Types in Trait Definitions
|
||||
### Specifying Placeholder Types in Trait Definitions with Associated Types
|
||||
|
||||
*Associated types* connect a type placeholder with a trait such that the trait
|
||||
method definitions can use these placeholder types in their signatures. The
|
||||
@@ -15,7 +15,7 @@ trait is implemented.
|
||||
|
||||
We’ve described most of the advanced features in this chapter as being rarely
|
||||
needed. Associated types are somewhere in the middle: they’re used more rarely
|
||||
than features explained in the rest of the book, but more commonly than many of
|
||||
than features explained in the rest of the book but more commonly than many of
|
||||
the other features discussed in this chapter.
|
||||
|
||||
One example of a trait with an associated type is the `Iterator` trait that the
|
||||
@@ -28,6 +28,7 @@ shown in Listing 19-20.
|
||||
```rust
|
||||
pub trait Iterator {
|
||||
type Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
}
|
||||
```
|
||||
@@ -40,11 +41,9 @@ that it will return values of type `Option<Self::Item>`. Implementors of the
|
||||
`Iterator` trait will specify the concrete type for `Item`, and the `next`
|
||||
method will return an `Option` containing a value of that concrete type.
|
||||
|
||||
#### Associated Types vs. Generics
|
||||
|
||||
Associated types might seem like a similar concept to generics, in that they
|
||||
allow us to define a function without specifying what types it can handle. So
|
||||
why use associated types?
|
||||
Associated types might seem like a similar concept to generics, in that the
|
||||
latter allow us to define a function without specifying what types it can
|
||||
handle. So why use associated types?
|
||||
|
||||
Let’s examine the difference between the two concepts with an example from
|
||||
Chapter 13 that implements the `Iterator` trait on the `Counter` struct. In
|
||||
@@ -60,8 +59,8 @@ impl Iterator for Counter {
|
||||
// --snip--
|
||||
```
|
||||
|
||||
This syntax seems comparable to generics. So why not just define the `Iterator`
|
||||
trait with generics, as shown in Listing 19-21?
|
||||
This syntax seems comparable to that of generics. So why not just define the
|
||||
`Iterator` trait with generics, as shown in Listing 19-21?
|
||||
|
||||
```rust
|
||||
pub trait Iterator<T> {
|
||||
@@ -73,13 +72,13 @@ pub trait Iterator<T> {
|
||||
`Iterator` trait using generics</span>
|
||||
|
||||
The difference is that when using generics, as in Listing 19-21, we must
|
||||
annotate the types in each implementation. The reason is that we can also
|
||||
implement `Iterator<String> for Counter` or any other type, which would give us
|
||||
multiple implementations of `Iterator` for `Counter`. In other words, when a
|
||||
trait has a generic parameter, it can be implemented for a type multiple times,
|
||||
changing the concrete types of the generic type parameters each time. When we
|
||||
use the `next` method on `Counter`, we would have to provide type annotations
|
||||
to indicate which implementation of `Iterator` we want to use.
|
||||
annotate the types in each implementation; because we can also implement
|
||||
`Iterator<String> for Counter` or any other type, we could have multiple
|
||||
implementations of `Iterator` for `Counter`. In other words, when a trait has a
|
||||
generic parameter, it can be implemented for a type multiple times, changing
|
||||
the concrete types of the generic type parameters each time. When we use the
|
||||
`next` method on `Counter`, we would have to provide type annotations to
|
||||
indicate which implementation of `Iterator` we want to use.
|
||||
|
||||
With associated types, we don’t need to annotate types because we can’t
|
||||
implement a trait on a type multiple times. In Listing 19-20 with the
|
||||
@@ -155,20 +154,20 @@ trait Add<RHS=Self> {
|
||||
```
|
||||
|
||||
This code should look generally familiar: a trait with one method and an
|
||||
associated type. The new part is `RHS=Self` in the angle brackets: this syntax
|
||||
is called *default type parameters*. The `RHS` generic type parameter (short
|
||||
for “right hand side”) defines the type of the `rhs` parameter in the `add`
|
||||
method. If we don’t specify a concrete type for `RHS` when we implement the
|
||||
`Add` trait, the type of `RHS` will default to `Self`, which will be the type
|
||||
we’re implementing `Add` on.
|
||||
associated type. The new part is `RHS=Self`: this syntax is called *default
|
||||
type parameters*. The `RHS` generic type parameter (short for “right hand
|
||||
side”) defines the type of the `rhs` parameter in the `add` method. If we don’t
|
||||
specify a concrete type for `RHS` when we implement the `Add` trait, the type
|
||||
of `RHS` will default to `Self`, which will be the type we’re implementing
|
||||
`Add` on.
|
||||
|
||||
When we implemented `Add` for `Point`, we used the default for `RHS` because we
|
||||
wanted to add two `Point` instances. Let’s look at an example of implementing
|
||||
the `Add` trait where we want to customize the `RHS` type rather than using the
|
||||
default.
|
||||
|
||||
We have two structs holding values in different units, `Millimeters` and
|
||||
`Meters`. We want to add values in millimeters to values in meters and have the
|
||||
We have two structs, `Millimeters` and `Meters`, holding values in different
|
||||
units. We want to add values in millimeters to values in meters and have the
|
||||
implementation of `Add` do the conversion correctly. We can implement `Add` for
|
||||
`Millimeters` with `Meters` as the `RHS`, as shown in Listing 19-23.
|
||||
|
||||
@@ -195,32 +194,32 @@ impl Add<Meters> for Millimeters {
|
||||
To add `Millimeters` and `Meters`, we specify `impl Add<Meters>` to set the
|
||||
value of the `RHS` type parameter instead of using the default of `Self`.
|
||||
|
||||
We use default type parameters in two main ways:
|
||||
You’ll use default type parameters in two main ways:
|
||||
|
||||
* To extend a type without breaking existing code
|
||||
* To allow customization in specific cases most users won’t need
|
||||
|
||||
The standard library’s `Add` trait is an example of the second purpose:
|
||||
usually, you’ll add two like types, but the `Add` trait provides the ability
|
||||
for customizing beyond that. Using a default type parameter in the `Add` trait
|
||||
usually, you’ll add two like types, but the `Add` trait provides the ability to
|
||||
customize beyond that. Using a default type parameter in the `Add` trait
|
||||
definition means you don’t have to specify the extra parameter most of the
|
||||
time. In other words, a bit of implementation boilerplate isn’t needed, making
|
||||
it easier to use the trait.
|
||||
|
||||
The first purpose is similar to the second but in reverse: if we want to add a
|
||||
type parameter to an existing trait, we can give it a default to let us extend
|
||||
the functionality of the trait without breaking the existing implementation
|
||||
code.
|
||||
The first purpose is similar to the second but in reverse: if you want to add a
|
||||
type parameter to an existing trait, you can give it a default to allow
|
||||
extension of the functionality of the trait without breaking the existing
|
||||
implementation code.
|
||||
|
||||
### Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name
|
||||
|
||||
Nothing in Rust prevents a trait from having a method with the same name as
|
||||
another trait’s method, nor does Rust prevent us from implementing both traits
|
||||
another trait’s method, nor does Rust prevent you from implementing both traits
|
||||
on one type. It’s also possible to implement a method directly on the type with
|
||||
the same name as methods from traits.
|
||||
|
||||
When calling methods with the same name, we need to tell Rust which one we want
|
||||
to use. Consider the code in Listing 19-24 where we’ve defined two traits,
|
||||
When calling methods with the same name, you’ll need to tell Rust which one you
|
||||
want to use. Consider the code in Listing 19-24 where we’ve defined two traits,
|
||||
`Pilot` and `Wizard`, that both have a method called `fly`. We then implement
|
||||
both traits on a type `Human` that already has a method named `fly` implemented
|
||||
on it. Each `fly` method does something different.
|
||||
@@ -257,9 +256,9 @@ impl Human {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">Listing 19-24: Two traits defined to have a `fly` method
|
||||
and implementations of those traits on the `Human` type in addition to a `fly`
|
||||
method on `Human` directly</span>
|
||||
<span class="caption">Listing 19-24: Two traits are defined to have a `fly`
|
||||
method and are implemented on the `Human` type, and a `fly` method is
|
||||
implemented on `Human` directly</span>
|
||||
|
||||
When we call `fly` on an instance of `Human`, the compiler defaults to calling
|
||||
the method that is directly implemented on the type, as shown in Listing 19-25.
|
||||
@@ -304,7 +303,7 @@ fn main() {
|
||||
<span class="caption">Listing 19-25: Calling `fly` on an instance of
|
||||
`Human`</span>
|
||||
|
||||
Running this code will print `*waving arms furiously*`, which shows that Rust
|
||||
Running this code will print `*waving arms furiously*`, showing that Rust
|
||||
called the `fly` method implemented on `Human` directly.
|
||||
|
||||
To call the `fly` methods from either the `Pilot` trait or the `Wizard` trait,
|
||||
@@ -355,8 +354,9 @@ want to call</span>
|
||||
|
||||
Specifying the trait name before the method name clarifies to Rust which
|
||||
implementation of `fly` we want to call. We could also write
|
||||
`Human::fly(&person)`, which is equivalent to `person.fly()` that we used in
|
||||
Listing 19-26 but is a bit longer to write if we don’t need to disambiguate.
|
||||
`Human::fly(&person)`, which is equivalent to the `person.fly()` that we used
|
||||
in Listing 19-26, but this is a bit longer to write if we don’t need to
|
||||
disambiguate.
|
||||
|
||||
Running this code prints the following:
|
||||
|
||||
@@ -367,12 +367,12 @@ Up!
|
||||
```
|
||||
|
||||
Because the `fly` method takes a `self` parameter, if we had two *types* that
|
||||
both implement one *trait*, Rust can figure out which implementation of a trait
|
||||
to use based on the type of `self`.
|
||||
both implement one *trait*, Rust could figure out which implementation of a
|
||||
trait to use based on the type of `self`.
|
||||
|
||||
However, associated functions that are part of traits don’t have a `self`
|
||||
parameter. When two types in the same scope implement that trait, Rust can’t
|
||||
figure out which type we mean unless we use *fully qualified syntax*. For
|
||||
figure out which type you mean unless you use *fully qualified syntax*. For
|
||||
example, the `Animal` trait in Listing 19-27 has the associated function
|
||||
`baby_name`, the implementation of `Animal` for the struct `Dog`, and the
|
||||
associated function `baby_name` defined on `Dog` directly.
|
||||
@@ -404,8 +404,8 @@ fn main() {
|
||||
```
|
||||
|
||||
<span class="caption">Listing 19-27: A trait with an associated function and a
|
||||
type that has an associated function with the same name that also implements
|
||||
the trait</span>
|
||||
type with an associated function of the same name that also implements the
|
||||
trait</span>
|
||||
|
||||
This code is for an animal shelter that wants to name all puppies Spot, which
|
||||
is implemented in the `baby_name` associated function that is defined on `Dog`.
|
||||
@@ -454,9 +454,8 @@ error[E0283]: type annotations required: cannot resolve `_: Animal`
|
||||
```
|
||||
|
||||
To disambiguate and tell Rust that we want to use the implementation of
|
||||
`Animal` for `Dog`, we need to use *fully qualified syntax*, which is the most
|
||||
specific we can be when calling a function. Listing 19-29 demonstrates how to
|
||||
use fully qualified syntax.
|
||||
`Animal` for `Dog`, we need to use fully qualified syntax. Listing 19-29
|
||||
demonstrates how to use fully qualified syntax.
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
@@ -504,18 +503,18 @@ In general, fully qualified syntax is defined as follows:
|
||||
```
|
||||
|
||||
For associated functions, there would not be a `receiver`: there would only be
|
||||
the list of other arguments. We could use fully qualified syntax everywhere
|
||||
that we call functions or methods. However, we’re allowed to omit any part of
|
||||
this syntax that Rust can figure out from other information in the program. We
|
||||
the list of other arguments. You could use fully qualified syntax everywhere
|
||||
that you call functions or methods. However, you’re allowed to omit any part of
|
||||
this syntax that Rust can figure out from other information in the program. You
|
||||
only need to use this more verbose syntax in cases where there are multiple
|
||||
implementations that use the same name and Rust needs help to identify which
|
||||
implementation we want to call.
|
||||
implementation you want to call.
|
||||
|
||||
### Using Supertraits to Require One Trait’s Functionality Within Another Trait
|
||||
|
||||
Sometimes, we might need one trait to use another trait’s functionality. In
|
||||
this case, we need to rely on the dependent trait also being implemented. The
|
||||
trait we’re relying on is a *supertrait* of the trait we’re implementing.
|
||||
Sometimes, you might need one trait to use another trait’s functionality. In
|
||||
this case, you need to rely on the dependent trait’s also being implemented.
|
||||
The trait you rely on is a *supertrait* of the trait you’re implementing.
|
||||
|
||||
For example, let’s say we want to make an `OutlinePrint` trait with an
|
||||
`outline_print` method that will print a value framed in asterisks. That is,
|
||||
@@ -533,10 +532,10 @@ call `outline_print` on a `Point` instance that has `1` for `x` and `3` for
|
||||
|
||||
In the implementation of `outline_print`, we want to use the `Display` trait’s
|
||||
functionality. Therefore, we need to specify that the `OutlinePrint` trait will
|
||||
only work for types that also implement `Display` and provide the functionality
|
||||
work only for types that also implement `Display` and provide the functionality
|
||||
that `OutlinePrint` needs. We can do that in the trait definition by specifying
|
||||
`OutlinePrint: Display`. This technique is similar to adding a trait bound to
|
||||
the trait. Listing 19-30 shows an implementation of the `OutlinePrint` trait:
|
||||
the trait. Listing 19-30 shows an implementation of the `OutlinePrint` trait.
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
@@ -561,9 +560,10 @@ requires the functionality from `Display`</span>
|
||||
|
||||
Because we’ve specified that `OutlinePrint` requires the `Display` trait, we
|
||||
can use the `to_string` function that is automatically implemented for any type
|
||||
that implements `Display`. If we tried to use `to_string` without adding `:
|
||||
Display` after the trait name, we’d get an error saying that no method named
|
||||
`to_string` was found for the type `&Self` in the current scope.
|
||||
that implements `Display`. If we tried to use `to_string` without adding a
|
||||
colon and specifying the `Display` trait after the trait name, we’d get an
|
||||
error saying that no method named `to_string` was found for the type `&Self` in
|
||||
the current scope.
|
||||
|
||||
Let’s see what happens when we try to implement `OutlinePrint` on a type that
|
||||
doesn’t implement `Display`, such as the `Point` struct:
|
||||
@@ -617,7 +617,7 @@ Then implementing the `OutlinePrint` trait on `Point` will compile
|
||||
successfully, and we can call `outline_print` on a `Point` instance to display
|
||||
it within an outline of asterisks.
|
||||
|
||||
### The Newtype Pattern to Implement External Traits on External Types
|
||||
### Using the Newtype Pattern to Implement External Traits on External Types
|
||||
|
||||
In Chapter 10 in the “Implementing a Trait on a Type” section, we mentioned the
|
||||
orphan rule that states we’re allowed to implement a trait on a type as long as
|
||||
@@ -665,15 +665,14 @@ tuple. Then we can use the functionality of the `Display` type on `Wrapper`.
|
||||
|
||||
The downside of using this technique is that `Wrapper` is a new type, so it
|
||||
doesn’t have the methods of the value it’s holding. We would have to implement
|
||||
all the methods of `Vec` directly on `Wrapper` so it can delegate to `self.0`,
|
||||
allowing us to treat `Wrapper` exactly like a `Vec`. If we wanted the new type
|
||||
to have every method the inner type has, implementing the `Deref` trait
|
||||
(discussed in Chapter 15 in the “Treating Smart Pointers like Regular
|
||||
References with the `Deref` Trait” section) on the `Wrapper` to return the
|
||||
inner type would be a solution. If we don’t want the `Wrapper` type to have all
|
||||
the methods of the inner type, in order to restrict the `Wrapper` type’s
|
||||
behavior for example, we would have to implement just the methods we do want
|
||||
manually.
|
||||
all the methods of `Vec` directly on `Wrapper` such that the methods delegate
|
||||
to `self.0`, which would allow us to treat `Wrapper` exactly like a `Vec`. If
|
||||
we wanted the new type to have every method the inner type has, implementing
|
||||
the `Deref` trait (discussed in Chapter 15 in the “Treating Smart Pointers like
|
||||
Regular References with the `Deref` Trait” section) on the `Wrapper` to return
|
||||
the inner type would be a solution. If we don’t want the `Wrapper` type to have
|
||||
all the methods of the inner type—for example, to restrict the `Wrapper` type’s
|
||||
behavior—we would have to implement just the methods we do want manually.
|
||||
|
||||
Now you know how the newtype pattern is used in relation to traits; it’s also a
|
||||
useful pattern even when traits are not involved. Let’s switch focus and look
|
||||
|
||||
@@ -11,14 +11,13 @@ discuss the `!` type and dynamically sized types.
|
||||
|
||||
### Using the Newtype Pattern for Type Safety and Abstraction
|
||||
|
||||
The newtype pattern is useful for other tasks beyond what we’ve discussed so
|
||||
far, including statically enforcing that values are never confused and as an
|
||||
indication of the units of a value. You saw an example of using newtypes to
|
||||
indicate units in Listing 19-23: recall that the `Millimeters` and `Meters`
|
||||
structs wrapped `u32` values in a newtype. If we wrote a function with a
|
||||
parameter of type `Millimeters`, we couldn’t compile a program that
|
||||
accidentally tried to call that function with a value of type `Meters` or a
|
||||
plain `u32`.
|
||||
The newtype pattern is useful for tasks beyond those we’ve discussed so far,
|
||||
including statically enforcing that values are never confused and indicating
|
||||
the units of a value. You saw an example of using newtypes to indicate units in
|
||||
Listing 19-23: recall that the `Millimeters` and `Meters` structs wrapped `u32`
|
||||
values in a newtype. If we wrote a function with a parameter of type
|
||||
`Millimeters`, we couldn’t compile a program that accidentally tried to call
|
||||
that function with a value of type `Meters` or a plain `u32`.
|
||||
|
||||
Another use of the newtype pattern is in abstracting away some implementation
|
||||
details of a type: the new type can expose a public API that is different from
|
||||
@@ -34,7 +33,7 @@ internally. The newtype pattern is a lightweight way to achieve encapsulation
|
||||
to hide implementation details, which we discussed in the “Encapsulation that
|
||||
Hides Implementation Details” section of Chapter 17.
|
||||
|
||||
### Type Aliases Create Type Synonyms
|
||||
### Creating Type Synonyms with Type Aliases
|
||||
|
||||
Along with the newtype pattern, Rust provides the ability to declare a *type
|
||||
alias* to give an existing type another name. For this we use the `type`
|
||||
@@ -145,7 +144,7 @@ type Result<T> = Result<T, std::io::Error>;
|
||||
```
|
||||
|
||||
Because this declaration is in the `std::io` module, we can use the fully
|
||||
qualified alias `std::io::Result<T>`; that is, a `Result<T, E>` with the `E`
|
||||
qualified alias `std::io::Result<T>`—that is, a `Result<T, E>` with the `E`
|
||||
filled in as `std::io::Error`. The `Write` trait function signatures end up
|
||||
looking like this:
|
||||
|
||||
@@ -162,9 +161,9 @@ pub trait Write {
|
||||
The type alias helps in two ways: it makes code easier to write *and* it gives
|
||||
us a consistent interface across all of `std::io`. Because it’s an alias, it’s
|
||||
just another `Result<T, E>`, which means we can use any methods that work on
|
||||
`Result<T, E>` with it, as well as special syntax like `?`.
|
||||
`Result<T, E>` with it, as well as special syntax like the `?` operator.
|
||||
|
||||
### The `!` Never Type that Never Returns
|
||||
### The Never Type that Never Returns
|
||||
|
||||
Rust has a special type named `!` that’s known in type theory lingo as the
|
||||
*empty type* because it has no values. We prefer to call it the *never type*
|
||||
@@ -182,7 +181,7 @@ never are called *diverging functions*. We can’t create values of the type `!`
|
||||
so `bar` can never possibly return.
|
||||
|
||||
But what use is a type you can never create values for? Recall the code from
|
||||
Listing 2-5; we’ve reproduced it here in Listing 19-34.
|
||||
Listing 2-5; we’ve reproduced part of it here in Listing 19-34.
|
||||
|
||||
```rust
|
||||
# let guess = "3";
|
||||
@@ -210,7 +209,7 @@ let guess = match guess.trim().parse() {
|
||||
```
|
||||
|
||||
The type of `guess` in this code would have to be an integer *and* a string,
|
||||
and Rust requires that `guess` can only have one type. So what does `continue`
|
||||
and Rust requires that `guess` have only one type. So what does `continue`
|
||||
return? How were we allowed to return a `u32` from one arm and have another arm
|
||||
that ends with `continue` in Listing 19-34?
|
||||
|
||||
@@ -241,10 +240,10 @@ impl<T> Option<T> {
|
||||
```
|
||||
|
||||
In this code, the same thing happens as in the `match` in Listing 19-34: Rust
|
||||
sees that `val` has the type `T` and `panic!` has the type `!` so the result of
|
||||
the overall `match` expression is `T`. This code works because `panic!` doesn’t
|
||||
produce a value; it ends the program. In the `None` case, we won’t be returning
|
||||
a value from `unwrap`, so this code is valid.
|
||||
sees that `val` has the type `T` and `panic!` has the type `!`, so the result
|
||||
of the overall `match` expression is `T`. This code works because `panic!`
|
||||
doesn’t produce a value; it ends the program. In the `None` case, we won’t be
|
||||
returning a value from `unwrap`, so this code is valid.
|
||||
|
||||
One final expression that has the type `!` is a `loop`:
|
||||
|
||||
@@ -260,13 +259,13 @@ Here, the loop never ends, so `!` is the value of the expression. However, this
|
||||
wouldn’t be true if we included a `break`, because the loop would terminate
|
||||
when it got to the `break`.
|
||||
|
||||
### Dynamically Sized Types and `Sized`
|
||||
### Dynamically Sized Types and the `Sized` Trait
|
||||
|
||||
Due to Rust’s need to know certain details, such as how much space to allocate
|
||||
for a value of a particular type, there is a corner of its type system that can
|
||||
be confusing: the concept of *dynamically sized types*. Sometimes referred to
|
||||
as *DSTs* or *unsized types*, these types let us write code using values whose
|
||||
size we can only know at runtime.
|
||||
size we can know only at runtime.
|
||||
|
||||
Let’s dig into the details of a dynamically sized type called `str`, which
|
||||
we’ve been using throughout the book. That’s right, not `&str`, but `str` on
|
||||
@@ -288,18 +287,18 @@ holding a dynamically sized type.
|
||||
|
||||
So what do we do? In this case, you already know the answer: we make the types
|
||||
of `s1` and `s2` a `&str` rather than a `str`. Recall that in the “String
|
||||
Slices” section of Chapter 4 we said the slice data structure stores the
|
||||
Slices” section of Chapter 4, we said the slice data structure stores the
|
||||
starting position and the length of the slice.
|
||||
|
||||
So although a `&T` is a single value that stores the memory address of where
|
||||
the `T` is located, a `&str` is *two* values: the address of the `str` and its
|
||||
length. As such, we can know the size of a `&str` value at compile time: it’s
|
||||
two times the size of a `usize` in length. That is, we always know the size of
|
||||
a `&str`, no matter how long the string it refers to is. In general, this is
|
||||
the way in which dynamically sized types are used in Rust: they have an extra
|
||||
bit of metadata that stores the size of the dynamic information. The golden
|
||||
rule of dynamically sized types is that we must always put values of
|
||||
dynamically sized types behind a pointer of some kind.
|
||||
twice the length of a `usize`. That is, we always know the size of a `&str`, no
|
||||
matter how long the string it refers to is. In general, this is the way in
|
||||
which dynamically sized types are used in Rust: they have an extra bit of
|
||||
metadata that stores the size of the dynamic information. The golden rule of
|
||||
dynamically sized types is that we must always put values of dynamically sized
|
||||
types behind a pointer of some kind.
|
||||
|
||||
We can combine `str` with all kinds of pointers: for example, `Box<str>` or
|
||||
`Rc<str>`. In fact, you’ve seen this before but with a different dynamically
|
||||
@@ -329,7 +328,7 @@ fn generic<T: Sized>(t: T) {
|
||||
}
|
||||
```
|
||||
|
||||
By default, generic functions will only work on types that have a known size at
|
||||
By default, generic functions will work only on types that have a known size at
|
||||
compile time. However, you can use the following special syntax to relax this
|
||||
restriction:
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ closures, which include function pointers and returning closures.
|
||||
### Function Pointers
|
||||
|
||||
We’ve talked about how to pass closures to functions; you can also pass regular
|
||||
functions to functions! This technique is useful when we want to pass a
|
||||
function we’ve already defined rather than defining a new closure. We do this
|
||||
using function pointers to allow us to use functions as arguments to other
|
||||
functions to functions! This technique is useful when you want to pass a
|
||||
function you’ve already defined rather than defining a new closure. Doing this
|
||||
with function pointers will allow you to use functions as arguments to other
|
||||
functions. Functions coerce to the type `fn` (with a lowercase f), not to be
|
||||
confused with the `Fn` closure trait. The `fn` type is called a function
|
||||
pointer. The syntax for specifying that a parameter is a function pointer is
|
||||
confused with the `Fn` closure trait. The `fn` type is called a *function
|
||||
pointer*. The syntax for specifying that a parameter is a function pointer is
|
||||
similar to that of closures, as shown in Listing 19-35.
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
@@ -45,7 +45,7 @@ parameter type directly rather than declaring a generic type parameter with one
|
||||
of the `Fn` traits as a trait bound.
|
||||
|
||||
Function pointers implement all three of the closure traits (`Fn`, `FnMut`, and
|
||||
`FnOnce`), so we can always pass a function pointer as an argument for a
|
||||
`FnOnce`), so you can always pass a function pointer as an argument for a
|
||||
function that expects a closure. It’s best to write functions using a generic
|
||||
type and one of the closure traits so your functions can accept either
|
||||
functions or closures.
|
||||
@@ -54,7 +54,7 @@ An example of where you would want to only accept `fn` and not closures is when
|
||||
interfacing with external code that doesn’t have closures: C functions can
|
||||
accept functions as arguments, but C doesn’t have closures.
|
||||
|
||||
As an example of where we can use either a closure defined inline or a named
|
||||
As an example of where you could use either a closure defined inline or a named
|
||||
function, let’s look at a use of `map`. To use the `map` function to turn a
|
||||
vector of numbers into a vector of strings, we could use a closure, like this:
|
||||
|
||||
@@ -88,12 +88,12 @@ up compiling to the same code, so use whichever style is clearer to you.
|
||||
|
||||
### Returning Closures
|
||||
|
||||
Closures are represented by traits, which means we can’t return closures
|
||||
directly. In most cases where we might want to return a trait, we can instead
|
||||
Closures are represented by traits, which means you can’t return closures
|
||||
directly. In most cases where you might want to return a trait, you can instead
|
||||
use the concrete type that implements the trait as the return value of the
|
||||
function. But we can’t do that with closures because they don’t have a concrete
|
||||
type that is returnable; we’re not allowed to use the function pointer `fn` as
|
||||
a return type, for example.
|
||||
function. But you can’t do that with closures because they don’t have a
|
||||
concrete type that is returnable; you’re not allowed to use the function
|
||||
pointer `fn` as a return type, for example.
|
||||
|
||||
The following code tries to return a closure directly, but it won’t compile:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user