Skip to content

Commit

Permalink
Ch. 17.03 (NoStarch edits): first section
Browse files Browse the repository at this point in the history
  • Loading branch information
chriskrycho committed Jan 15, 2025
1 parent 758145c commit 793e4f0
Showing 1 changed file with 68 additions and 62 deletions.
130 changes: 68 additions & 62 deletions src/ch17-03-more-futures.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Working With Any Number of Futures
## Working with Any Number of Futures

When we switched from using two futures to three in the previous section, we
also had to switch from using `join` to using `join3`. It would be annoying to
Expand All @@ -16,17 +16,18 @@ Thus, we could rewrite the code from Listing 17-13 to use `join!` instead of

</Listing>

This is definitely a nice improvement over needing to swap between `join` and
`join3` and `join4` and so on! However, even this macro form only works when we
know the number of futures ahead of time. In real-world Rust, though, pushing
futures into a collection and then waiting on some or all the futures in that
collection to complete is a common pattern.
This is definitely an improvement over swapping between `join` and
`join3` and `join4` and so on! However, even this macro form only works
when we know the number of futures ahead of time. In real-world Rust,
though, pushing futures into a collection and then waiting on some or
all the futures of them to complete is a common pattern.

To check all the futures in some collection, we’ll need to iterate over and
join on _all_ of them. The `trpl::join_all` function accepts any type which
implements the `Iterator` trait, which we learned about back in Chapter 13, so
it seems like just the ticket. Let’s try putting our futures in a vector, and
replace `join!` with `join_all`.
join on _all_ of them. The `trpl::join_all` function accepts any type that
implements the `Iterator` trait, which you learned about back in [The Iterator
Trait and the `next` Method][iterator-trait]<!-- ignore --> Chapter 13, so
it seems like just the ticket. Let’s try putting our futures in a vector and
replacing `join!` with `join_all` as show in Listing 17-15.

<Listing number="17-15" caption="Storing anonymous futures in a vector and calling `join_all`">

Expand All @@ -36,7 +37,7 @@ replace `join!` with `join_all`.

</Listing>

Unfortunately, this doesn’t compile. Instead, we get this error:
Unfortunately, this code doesn’t compile. Instead, we get this error:

<!-- manual-regeneration
cd listings/ch17-async-await/listing-17-15/
Expand All @@ -55,51 +56,52 @@ error[E0308]: mismatched types
| ----- the found `async` block
...
45 | let futures = vec![tx1_fut, rx_fut, tx_fut];
| ^^^^^^ expected `async` block, found a different `async` block
| ^^^^^^ expected `async` block, found a
different `async` block
|
= note: expected `async` block `{async block@src/main.rs:10:23: 10:33}`
found `async` block `{async block@src/main.rs:24:22: 24:27}`
= note: no two async blocks, even if identical, have the same type
= help: consider pinning your async block and casting it to a trait object
```

This might be surprising. After all, none of them return anything, so each
block produces a `Future<Output = ()>`. However, `Future` is a trait, not a
concrete type. The concrete types are the individual data structures generated
by the compiler for async blocks. You can’t put two different hand-written
structs in a `Vec`, and the same thing applies to the different structs
generated by the compiler.
This might be surprising. After all, none of the async blocks returns anything,
so each one produces a `Future<Output = ()>`. Remember that `Future` is a trait,
though, and that the compiler creates a unique enum for each async block. You
can’t put two different hand-written structs in a `Vec`, and the same rule
applies to the different enums generated by the compiler.

To make this work, we need to use _trait objects_, just as we did in [“Returning
Errors from the run function”][dyn]<!-- ignore --> in Chapter 12. (We’ll cover trait objects
in detail in Chapter 18.) Using trait objects lets us treat each of the
anonymous futures produced by these types as the same type, because all of them
implement the `Future` trait.
Errors from the run function”][dyn]<!-- ignore --> in Chapter 12. (We’ll cover
trait objects in detail in Chapter 18.) Using trait objects lets us treat each
of the anonymous futures produced by these types as the same type, because all
of them implement the `Future` trait.

> Note: In Chapter 8, we discussed another way to include multiple types in a
> `Vec`: using an enum to represent each of the different types which can
> appear in the vector. We can’t do that here, though. For one thing, we have
> no way to name the different types, because they are anonymous. For another,
> the reason we reached for a vector and `join_all` in the first place was to be
> able to work with a dynamic collection of futures where we don’t know what
> they will all be until runtime.
> Note: In the Chapter 8 section [Using an Enum to Store Multiple
> Values][enum-alt]<!-- ignore -->, we discussed another way to include multiple
> types in a `Vec`: using an enum to represent each type that can appear in the
> vector. We can’t do that here, though. For one thing, we have no way to name
> the different types, because they are anonymous. For another, the reason we
> reached for a vector and `join_all` in the first place was to be able to work
> with a dynamic collection of futures where we don’t know what they will all be
> until runtime.
We start by wrapping each of the futures in the `vec!` in a `Box::new`, as shown
in Listing 17-16.
We start by wrapping each future in the `vec!` in a `Box::new`, as shown in
Listing 17-16.

<Listing number="17-16" caption="Trying to use `Box::new` to align the types of the futures in a `Vec`" file-name="src/main.rs">
<Listing number="17-16" caption="Using `Box::new` to align the types of the futures in a `Vec`" file-name="src/main.rs">

```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch17-async-await/listing-17-16/src/main.rs:here}}
```

</Listing>

Unfortunately, this still doesn’t compile. In fact, we have the same basic
error we did before, but we get one for both the second and third `Box::new`
calls, and we also get new errors referring to the `Unpin` trait. We will come
back to the `Unpin` errors in a moment. First, let’s fix the type errors on the
`Box::new` calls, by explicitly annotating the type of the `futures` variable:
Unfortunately, this code still doesn’t compile. In fact, we get the same basic
error we got before for both the second and third `Box::new` calls, as well as
new errors referring to the `Unpin` trait. We’ll come back to the `Unpin` errors
in a moment. First, let’s fix the type errors on the `Box::new` calls by
explicitly annotating the type of the `futures` variable (see Listing 17-17).

<Listing number="17-17" caption="Fixing the rest of the type mismatch errors by using an explicit type declaration" file-name="src/main.rs">

Expand All @@ -109,17 +111,18 @@ back to the `Unpin` errors in a moment. First, let’s fix the type errors on th

</Listing>

The type we had to write here is a little involved, so let’s walk through it:
This type declaration is a little involved, so let’s walk through it:

- The innermost type is the future itself. We note explicitly that the output of
the future is the unit type `()` by writing `Future<Output = ()>`.
- Then we annotate the trait with `dyn` to mark it as dynamic.
- The entire trait reference is wrapped in a `Box`.
- Finally, we state explicitly that `futures` is a `Vec` containing these items.
1. The innermost type is the future itself. We note explicitly that the output
of the future is the unit type `()` by writing `Future<Output = ()>`.
2. Then we annotate the trait with `dyn` to mark it as dynamic.
3. The entire trait reference is wrapped in a `Box`.
4. Finally, we state explicitly that `futures` is a `Vec` containing these
items.

That already made a big difference. Now when we run the compiler, we only have
the errors mentioning `Unpin`. Although there are three of them, notice that
each is very similar in its contents.
That already made a big difference. Now when we run the compiler, we get only
the errors mentioning `Unpin`. Although there are three of them, their contents
are very similar.

<!-- manual-regeneration
cd listings/ch17-async-await/listing-17-16
Expand Down Expand Up @@ -239,7 +242,7 @@ tell us that the first async block (`src/main.rs:8:23: 20:10`) does not
implement the `Unpin` trait, and suggests using `pin!` or `Box::pin` to resolve
it. Later in the chapter, we’ll dig into a few more details about `Pin` and
`Unpin`. For the moment, though, we can just follow the compiler’s advice to get
unstuck! In Listing 17-18, we start by updating the type annotation for
unstuck. In Listing 17-18, we start by updating the type annotation for
`futures`, with a `Pin` wrapping each `Box`. Second, we use `Box::pin` to pin
the futures themselves.

Expand Down Expand Up @@ -270,14 +273,14 @@ received 'you'

Phew!

There’s a bit more we can explore here. For one thing, using `Pin<Box<T>>`
comes with a small amount of extra overhead from putting these futures on the
heap with `Box`—and we’re only doing that to get the types to line up. We don’t
actually _need_ the heap allocation, after all: these futures are local to this
particular function. As noted above, `Pin` is itself a wrapper type, so we can
get the benefit of having a single type in the `Vec`—the original reason we
reached for `Box`—without doing a heap allocation. We can use `Pin` directly
with each future, using the `std::pin::pin` macro.
There’s a bit more to explore here. For one thing, using `Pin<Box<T>>` adds a
small amount of overhead from putting these futures on the heap with `Box`—and
we’re only doing that to get the types to line up. We don’t actually _need_ the
heap allocation, after all: these futures are local to this particular function.
As noted before, `Pin` is itself a wrapper type, so we can get the benefit of
having a single type in the `Vec`—the original reason we reached for
`Box`—without doing a heap allocation. We can use `Pin` directly with each
future, using the `std::pin::pin` macro.

However, we must still be explicit about the type of the pinned reference;
otherwise Rust will still not know to interpret these as dynamic trait objects,
Expand Down Expand Up @@ -306,17 +309,18 @@ types. For example, in Listing 17-20, the anonymous future for `a` implements

</Listing>

We can use `trpl::join!` to await them, because it allows you to pass in
multiple future types and produces a tuple of those types. We _cannot_ use
`trpl::join_all`, because it requires the futures passed in all to have the same
type. Remember, that error is what got us started on this adventure with `Pin`!
We can use `trpl::join!` to await them, because it allows us to pass in multiple
future types and produces a tuple of those types. We _cannot_ use
`trpl::join_all`, because it requires all of the futures passed in to have the
same type. Remember, that error is what got us started on this adventure with
`Pin`!

This is a fundamental tradeoff: we can either deal with a dynamic number of
futures with `join_all`, as long as they all have the same type, or we can deal
with a set number of futures with the `join` functions or the `join!` macro,
even if they have different types. This is the same as working with any other
types in Rust, though. Futures are not special, even though we have some nice
syntax for working with them, and that is a good thing.
even if they have different types. This is the same scenario we’d face when
working with any other types in Rust. Futures are not special, even though we
have some nice syntax for working with them, and that’s a good thing.

### Racing futures

Expand Down Expand Up @@ -623,4 +627,6 @@ to consider first, though:
with any collection of futures.)

[dyn]: ch12-03-improving-error-handling-and-modularity.html
[enum-alt]: ch12-03-improving-error-handling-and-modularity.html#returning-errors-from-the-run-function
[async-program]: ch17-01-futures-and-syntax.html#our-first-async-program
[iterator-trait]: ch13-02-iterators.html#the-iterator-trait-and-the-next-method

0 comments on commit 793e4f0

Please sign in to comment.