Changes made during 2nd page review of chapter 19

This commit is contained in:
Carol (Nichols || Goulding)
2018-05-06 17:08:20 -04:00
parent 828f2cdf30
commit 509cb42ece
7 changed files with 180 additions and 182 deletions

View File

@@ -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>;

View File

@@ -10,13 +10,13 @@ make sure you have a grasp of all the features Rust has to offer.
In this chapter, well cover:
* Unsafe Rust: How to opt out of some of Rusts guarantees and take
* Unsafe Rust: how to opt out of some of Rusts 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
Its a panoply of Rust features with something for everyone! Lets dive in!

View File

@@ -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,
its better for it to reject some valid programs rather than accepting some
its 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, its not! In these cases, we can use unsafe code to tell the compiler,
trust me, I know what Im 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, its not! In these cases, you can use unsafe code to tell the compiler,
Trust me, I know what Im 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 didnt let us do unsafe operations, we
couldnt do certain tasks. Rust needs to allow us to do low-level systems
hardware is inherently unsafe. If Rust didnt let you do unsafe operations, you
couldnt 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. Lets 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. Lets 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 cant 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 cant 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:
Its important to understand that `unsafe` doesnt turn off the borrow checker
or disable any other of Rusts 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. Youll 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, well ensure the code inside an `unsafe` block will
that as the programmer, youll 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` well know that
unsafe operations to be inside blocks annotated with `unsafe` youll know that
any errors related to memory safety must be within an `unsafe` block. Keep
`unsafe` blocks small; youll 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.
Lets look at each of the four unsafe superpowers in turn: well also look at
Lets look at each of the four unsafe superpowers in turn. Well 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 isnt the dereference operator; its 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
cant 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
* Dont 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 Rusts guarantees dont 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; its 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 Rusts ownership rules dont
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 youll see in the next section,
“Calling an Unsafe Function or Method.” Another case is when building up safe
abstractions that the borrow checker doesnt understand. Well 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 its 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 its
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, well get an error:
When we try to compile the code in Listing 19-5, well 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, its difficult to ensure there
are no data races, which is why Rust considers mutable static variables to be
unsafe. Where possible, its 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 cant 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 {

View File

@@ -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 well
look at three advanced features of lifetimes that we havent 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 simplicitys 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 didnt parse correctly. A real implementation
would provide more error information and would return a structured data type
when parsing succeeds. We wont be discussing those details because they arent
@@ -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`. Rusts
compiler error message stated that lifetime parameters were required for these
references, and weve now added lifetime parameters.
Next, in Listing 19-14, well add a function that takes an instance of
`Context`, uses a `Parser` to parse that context, and returns what `parse`
returns. This code doesnt quite work:
returns. This code doesnt 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 cant 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 its
referencing the `Parser`s `Context`, which is referencing the string slice.
So, its 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
its referencing the `Parser` instances `Context`, which is referencing the
string slice. So, its 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, well try giving `Parser` and `Context` different lifetime parameters,
as shown in Listing 19-15. Well 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, Rusts advanced features are very specific. You wont often need the
syntax we described in this example, but in such situations, youll 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 doesnt 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

View File

@@ -4,7 +4,7 @@ We first covered traits in the “Traits: Defining Shared Behavior” section of
Chapter 10, but as with lifetimes, we didnt 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.
Weve described most of the advanced features in this chapter as being rarely
needed. Associated types are somewhere in the middle: theyre 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?
Lets 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 dont need to annotate types because we cant
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 dont 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
were 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 dont
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 were implementing
`Add` on.
When we implemented `Add` for `Point`, we used the default for `RHS` because we
wanted to add two `Point` instances. Lets 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:
Youll use default type parameters in two main ways:
* To extend a type without breaking existing code
* To allow customization in specific cases most users wont need
The standard librarys `Add` trait is an example of the second purpose:
usually, youll 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, youll 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 dont have to specify the extra parameter most of the
time. In other words, a bit of implementation boilerplate isnt 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 traits method, nor does Rust prevent us from implementing both traits
another traits method, nor does Rust prevent you from implementing both traits
on one type. Its 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 weve defined two traits,
When calling methods with the same name, youll need to tell Rust which one you
want to use. Consider the code in Listing 19-24 where weve 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 dont 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 dont 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 dont have a `self`
parameter. When two types in the same scope implement that trait, Rust cant
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, were 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, youre 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 Traits Functionality Within Another Trait
Sometimes, we might need one trait to use another traits functionality. In
this case, we need to rely on the dependent trait also being implemented. The
trait were relying on is a *supertrait* of the trait were implementing.
Sometimes, you might need one trait to use another traits functionality. In
this case, you need to rely on the dependent traits also being implemented.
The trait you rely on is a *supertrait* of the trait youre implementing.
For example, lets 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` traits
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 weve 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, wed 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, wed get an
error saying that no method named `to_string` was found for the type `&Self` in
the current scope.
Lets see what happens when we try to implement `OutlinePrint` on a type that
doesnt 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 were 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
doesnt have the methods of the value its 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 dont want the `Wrapper` type to have all
the methods of the inner type, in order to restrict the `Wrapper` types
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 dont want the `Wrapper` type to have
all the methods of the inner type—for example, to restrict the `Wrapper` types
behaviorwe would have to implement just the methods we do want manually.
Now you know how the newtype pattern is used in relation to traits; its also a
useful pattern even when traits are not involved. Lets switch focus and look

View File

@@ -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 weve 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 couldnt 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 weve 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 couldnt 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 its an alias, its
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 `!` thats 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 cant 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; weve reproduced it here in Listing 19-34.
Listing 2-5; weve 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!` doesnt
produce a value; it ends the program. In the `None` case, we wont 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!`
doesnt produce a value; it ends the program. In the `None` case, we wont 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
wouldnt 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 Rusts 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.
Lets dig into the details of a dynamically sized type called `str`, which
weve been using throughout the book. Thats 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: its
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, youve 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:

View File

@@ -6,12 +6,12 @@ closures, which include function pointers and returning closures.
### Function Pointers
Weve 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 weve 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 youve 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. Its 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 doesnt have closures: C functions can
accept functions as arguments, but C doesnt 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, lets 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 cant 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 cant 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 cant do that with closures because they dont have a concrete
type that is returnable; were not allowed to use the function pointer `fn` as
a return type, for example.
function. But you cant do that with closures because they dont have a
concrete type that is returnable; youre 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 wont compile: