Skip to content

Commit

Permalink
Ch. 17: Correct several points about Pin and Unpin
Browse files Browse the repository at this point in the history
Thanks very much to @tmandry (<[email protected]>) for the corrections
and feedback!
  • Loading branch information
chriskrycho committed Oct 15, 2024
1 parent d9202d8 commit 1a9f1eb
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 31 deletions.
50 changes: 34 additions & 16 deletions nostarch/chapter17.md
Original file line number Diff line number Diff line change
Expand Up @@ -2536,10 +2536,19 @@ For more information about an error, try `rustc --explain E0277`.
When we read this error message carefully, it not only tells us that we need to
pin the values, but also tells us why pinning is required. The `trpl::join_all`
function returns a struct called `JoinAll`. That struct is generic over a type
`F`, which is constrained to implement the `Future` trait. Finally, directly
awaiting a Future requires that the future in question implement the `Unpin`
trait. That’s a lot! But we can understand it, if we dive a little further into
how the `Future` type actually works, in particular around *pinning*.
`F`, which is constrained to implement the `Future` trait. Directly awaiting a
future with `await` pins the future implicitly. That’s why we don’t need to use
`pin!` everywhere we want to await futures.

However, we’re not directly awaiting a future here. Instead, we construct a new
future, `JoinAll`, by passing a collection of futures to the `join_all`
function. The signature for `join_all` produces requires that the type of the
items in the collection all implement the `Future` trait, and `Box<T>` only
implements `Future` if the `T` that it wraps is a future which implements the
`Unpin` trait.

That’s a lot! But we can understand it, if we dive a little further into how the
`Future` type actually works, in particular around *pinning*.

Let’s look again at the definition of `Future`:

Expand Down Expand Up @@ -2683,24 +2692,32 @@ that a given type does *not* need to uphold any particular guarantees about
whether the value in question can be moved.

Just as with `Send` and `Sync`, the compiler implements `Unpin` automatically
for all types where it can prove it is safe. Implementing `Unpin` manually is
unsafe because it requires *you* to uphold all the guarantees which make `Pin`
and `Unpin` safe yourself for a type with internal references. In practice,
this is a very rare thing to implement yourself!
for all types where it can prove it is safe. The special case, again similar to
`Send` and `Sync`, is the case where `Unpin` is *not* implemented for a type.
The notation for this is `impl !Unpin for SomeType`, where `SomeType` is the
name of a type which *does* need to uphold those guarantees to be safe whenever
a pointer to that type it is used in a `Pin`.

In other words, there are two things to keep in mind about the relationship
between `Pin` and `Unpin`. First, `Unpin` is the “normal” case, and `!Unpin` is
the special case. Second, whether a type implements `Unpin` or `!Unpin` *only*
matters when using a pinned pointer to that type like `Pin<&mut SomeType>`.

To make that concrete, think about a `String`: it has a length and the Unicode
characters which make it up. We can wrap a `String` in `Pin`, as seen in Figure
17-7. However
17-8. However, `String` automatically implements `Unpin`, the same as most other
types in Rust.

<img alt="Concurrent work flow" src="img/trpl17-08.svg" />

Figure 17-8: Pinning a String, with a dotted line indicating that the String
implements the `Unpin` trait, so it is not pinned.

This means that we can do things such as replace one string with another at the
exact same location in memory as in Figure 17-9. This doesn’t violate the `Pin`
contract because `String`—like most other types in Rust—implements `Unpin`,
because it has no internal references that make it unsafe to move around!
As a result, we can do things which would be illegal if `String` implemented
`!Unpin` instead, such as replace one string with another at the exact same
location in memory as in Figure 17-9. This doesn’t violate the `Pin` contract,
because `String` has no internal references that make it unsafe to move around!
That is precisely why it implements `Unpin` rather than `!Unpin`.

<img alt="Concurrent work flow" src="img/trpl17-09.svg" />

Expand All @@ -2709,9 +2726,10 @@ Figure 17-9: Replacing the String with an entirely different String in memory.
Now we know enough to understand the errors reported for that `join_all` call
from back in Listing 17-17. We originally tried to move the futures produced by
async blocks into a `Vec<Box<dyn Future<Output = ()>>>`, but as we’ve seen,
those futures may have internal references, so they don’t implement `Unpin`.
They need to be pinned, and then we can pass the `Pin` type into the `Vec`,
confident that the underlying data in the futures will *not* be moved.
those futures may have internal references, so they don’t automatically
implement `Unpin`. Once we pin them, we can pass the resulting `Pin` type into
the `Vec`, confident that the underlying data in the futures will *not* be
moved.

`Pin` and `Unpin` are mostly important for building lower-level libraries, or
when you’re building a runtime itself, rather than for day to day Rust code.
Expand Down
45 changes: 30 additions & 15 deletions src/ch17-05-traits-for-async.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@ it is not yet ready.

### Pinning and the Pin and Unpin Traits

<!-- TODO: get a *very* careful technical review of this section! -->

When we introduced the idea of pinning while working on Listing 17-17, we ran
into a very gnarly error message. Here is the relevant part of it again:

Expand Down Expand Up @@ -152,10 +150,19 @@ For more information about an error, try `rustc --explain E0277`.
When we read this error message carefully, it not only tells us that we need to
pin the values, but also tells us why pinning is required. The `trpl::join_all`
function returns a struct called `JoinAll`. That struct is generic over a type
`F`, which is constrained to implement the `Future` trait. Finally, directly
awaiting a Future requires that the future in question implement the `Unpin`
trait. That’s a lot! But we can understand it, if we dive a little further into
how the `Future` type actually works, in particular around *pinning*.
`F`, which is constrained to implement the `Future` trait. Directly awaiting a
future with `await` pins the future implicitly. That’s why we don’t need to use
`pin!` everywhere we want to await futures.

However, we’re not directly awaiting a future here. Instead, we construct a new
future, `JoinAll`, by passing a collection of futures to the `join_all`
function. The signature for `join_all` produces requires that the type of the
items in the collection all implement the `Future` trait, and `Box<T>` only
implements `Future` if the `T` that it wraps is a future which implements the
`Unpin` trait.

That’s a lot! But we can understand it, if we dive a little further into how the
`Future` type actually works, in particular around *pinning*.

Let’s look again at the definition of `Future`:

Expand Down Expand Up @@ -312,14 +319,21 @@ type does *not* need to uphold any particular guarantees about whether the value
in question can be moved.

Just as with `Send` and `Sync`, the compiler implements `Unpin` automatically
for all types where it can prove it is safe. Implementing `Unpin` manually is
unsafe because it requires *you* to uphold all the guarantees which make `Pin`
and `Unpin` safe yourself for a type with internal references. In practice,
this is a very rare thing to implement yourself!
for all types where it can prove it is safe. The special case, again similar to
`Send` and `Sync`, is the case where `Unpin` is *not* implemented for a type.
The notation for this is `impl !Unpin for SomeType`, where `SomeType` is the
name of a type which *does* need to uphold those guarantees to be safe whenever
a pointer to that type it is used in a `Pin`.

In other words, there are two things to keep in mind about the relationship
between `Pin` and `Unpin`. First, `Unpin` is the “normal” case, and `!Unpin` is
the special case. Second, whether a type implements `Unpin` or `!Unpin` *only*
matters when using a pinned pointer to that type like `Pin<&mut SomeType>`.

To make that concrete, think about a `String`: it has a length and the Unicode
characters which make it up. We can wrap a `String` in `Pin`, as seen in Figure
17-7. However
17-8. However, `String` automatically implements `Unpin`, the same as most other
types in Rust.

<figure>

Expand All @@ -329,10 +343,11 @@ characters which make it up. We can wrap a `String` in `Pin`, as seen in Figure

</figure>

This means that we can do things such as replace one string with another at the
exact same location in memory as in Figure 17-9. This doesn’t violate the `Pin`
contract because `String`—like most other types in Rust—implements `Unpin`,
because it has no internal references that make it unsafe to move around!
As a result, we can do things which would be illegal if `String` implemented
`!Unpin` instead, such as replace one string with another at the exact same
location in memory as in Figure 17-9. This doesn’t violate the `Pin` contract,
because `String` has no internal references that make it unsafe to move around!
That is precisely why it implements `Unpin` rather than `!Unpin`.

<figure>

Expand Down

0 comments on commit 1a9f1eb

Please sign in to comment.