started ch17.5

This commit is contained in:
darkicewolf50 2025-03-27 16:36:00 -06:00
parent f79a47defd
commit 0e0e0ea857
2 changed files with 204 additions and 2 deletions

View File

@ -7,8 +7,8 @@ name: Rust Checking and Testing
# whenever we create a pull request.
# other examples: [push] and [pull_request, push]
on:
# push:
# branches: [ "master" ]
push: # testing out the job
branches: [ "master" ]
pull_request:
branches: [ "master" ]

View File

@ -1 +1,203 @@
# A Closer Look at the Traits for Async
Sometime, you will encounter situations where you will need to understand a few more of these details.
A high level understanding is ok for most of day to day Rust writing.
In this chapter we will dig in just enough to help in those scenarios.
Diving even requires reading the documentation.
## The `Future` Trait
Now lets look at how the future trait works.
Here is how Rust defines it
```rust
use std::pin::Pin;
use std::task::{Context, Poll};
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
```
The trait definition includes a bunch of new types and also some syntax we haven't seen before.
First, `Future`'s associated type `Output` says what the future resolves to.
This is analogous to the `Item` associated type for the `IUterator` trait.
Second, `Future` also has the `poll` method, this takes a special `Pin` reference for its `self` parameter and a mutable reference to a `Context` type, and returns a `Poll<Self::Output>`.
For now we will focus on what the method returns, the `Poll` type
```rust
enum Poll<T> {
Ready(T),
Pending,
}
```
This `Pool` type is similar to an `Option`.
It has one variant that has a value, `Ready(T)`, and one which does not `Pending`.
`Poll` means something quite different form `Option`.
The `Pending` variant indicates that the future still has work to do, so the caller will need to check again later.
The `Ready` variant indicates that the future has finished its work and the `T` value is available.
Note that futures, the caller should not call `poll` again after the future has returned `Ready`.
Many of the futures will panic if polled again after becoming ready.
Futures that safe to poll again will say so explicitly in their documentation.
This is similar behavior to `Iterator::next`.
When toy see code that uses `await`, Rust compiles it under the hood to code that calls `poll`.
If you look back at the previous example, where we printed out the page title for a single URL once it resolved.
Rust compiles it into something kind of like this
```rust
match page_title(url).poll() {
Ready(page_title) => match page_title {
Some(title) => println!("The title for {url} was {title}"),
None => println!("{url} had no title"),
}
Pending => {
// But what goes here?
}
}
```
What should we do when the future is still `Pending`?
We need way to try again and again, until the future is finally ready.
We need a loop
```rust
let mut page_title_fut = page_title(url);
loop {
match page_title_fut.poll() {
Ready(value) => match page_title {
Some(title) => println!("The title for {url} was {title}"),
None => println!("{url} had no title"),
}
Pending => {
// continue
}
}
}
```
If Rust compiled it to exactly this code.
Every `await` would be blocking, exactly the opposite of what we are trying to do.
Instead Rust makes sure that the loop can hand off control to something that can pause work in this future to work on other futures and then check this again later.
This is something that async runtime, and this scheduling and coordination work is one of its main jobs.
Earlier we described waiting on `rx.recv`.
The `recv` call returns a future, and awaiting the future polls it.
We noted that a runtime will pause the future until it is ready with either `Some(message)` or `None` when the channel closes.
Now with the deeper understanding of the `Future` trait, and specifically `Future::poll`, we can now see how that works.
The runtime knows the future isn't ready when it returns `Poll::Pending`.
The runtime also knows the future *is* ready and advances it when `poll` returns `Poll::Ready(Some(message))` or `Poll::Ready(None)`
The exact details of how a runtime works, is something that will not be covered by this book.
The key is to see the basic mechanics of futures.
A runtime *polls* each future it is responsible for, putting the future beck to sleep when it is not yet ready.
## The `Pin` and `Unpin` Traits
When we introduced pinning, we run into this error message.
Here is the relevant part again
```
error[E0277]: `{async block@src/main.rs:10:23: 10:33}` cannot be unpinned
--> src/main.rs:48:33
|
48 | trpl::join_all(futures).await;
| ^^^^^ the trait `Unpin` is not implemented for `{async block@src/main.rs:10:23: 10:33}`, which is required by `Box<{async block@src/main.rs:10:23: 10:33}>: Future`
|
= note: consider using the `pin!` macro
consider using `Box::pin` if you need to access the pinned value outside of the current scope
= note: required for `Box<{async block@src/main.rs:10:23: 10:33}>` to implement `Future`
note: required by a bound in `futures_util::future::join_all::JoinAll`
--> file:///home/.cargo/registry/src/index.crates.io-6f17d22bba15001f/futures-util-0.3.30/src/future/join_all.rs:29:8
|
27 | pub struct JoinAll<F>
| ------- required by a bound in this struct
28 | where
29 | F: Future,
| ^^^^^^ required by this bound in `JoinAll`
```
This error message tells us not only that we need to pin the values but also why pinning is required.
The `trpl::join_all` function returns a struct called `JoinAll`.
The struct is a generic over a type `F` which is constrained to implement the `Future` trait.
Directly awaiting a future with `await` pins the future implicitly.
This is why we don't need to use `pin!` everywhere we want to await futures.
However we are 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` requires that the types of the items in the collection all implement the `Future` trait.
`Box<T>` implements `Future` only if the `T` wraps is a future that implements the `Unpin` trait.
Now we will dive a little deeper into how the `Future` trait actually works, in particular around *pinning*.
Lets look at the definition of pf the `Future` trait.
```rust
use std::pin::Pin;
use std::task::{Context, Poll};
pub trait Future {
type Output;
// Required method
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
```
The `cx` parameter and its `Context` type are the key to how a runtime actually knows when to check any given future while still being lazy.
The details of how that works are beyond the scope of this chapter, and you generally only need to think about this when writing a custom `Future` implementation.
Here we will focus on the type for `self` as this is the first time we have seen a method where `self` has a type annotation.
A type annotation for `self` is works like type annotations for other function parameters, but there are two key differences.
- It tells Rust what type `self` must be for the method to be called.
- It can't be just any time
- It is restricted to the type on which the method is implemented, a reference or smart pointer to the type, or a `Pin` wrapping a reference to that type.
We will see more on this syntax in Ch18.
For now know that if we want to poll a future to check whether it is `Pending` or `Ready(Output)`m we need a `Pin` wrapped mutable reference to the type.
`Pin` is a wrapper for pointer-like types such as `&`, `&mut`, `Box`, and `Rc`.
(Technically `Pin` works with types that implement the `Deref` or `DerefMut` traits but this is effectively equivalent to working only with pointers)
`Pin` is not a pointer itself.
It also doesn't have any behavior of its own like `Rc` and `Arc` do with reference counting.
This is purely a tool the compiler can use to enforce constraints on pointer usage.
Recall that `await` is implemented in terms of calls to `poll` start to explain the error message from before.
This was in terms of `Unpin`, not `Pin`.
How does `Pin` relate to `Unpin` and why does `Future` need `self` to be in a `Pin` type to call `poll`?