feat: added cleanscript
This commit is contained in:
145
ch17/ch17-00-async-await.html
Normal file
145
ch17/ch17-00-async-await.html
Normal file
@@ -0,0 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="fundamentals-of-asynchronous-programming-async-await-futures-and-streams"><a class="header" href="#fundamentals-of-asynchronous-programming-async-await-futures-and-streams">Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams</a></h1>
|
||||
<p>Many operations we ask the computer to do can take a while to finish. It would
|
||||
be nice if we could do something else while we’re waiting for those
|
||||
long-running processes to complete. Modern computers offer two techniques for
|
||||
working on more than one operation at a time: parallelism and concurrency. Our
|
||||
programs’ logic, however, is written in a mostly linear fashion. We’d like to
|
||||
be able to specify the operations a program should perform and points at which
|
||||
a function could pause and some other part of the program could run instead,
|
||||
without needing to specify up front exactly the order and manner in which each
|
||||
bit of code should run. <em>Asynchronous programming</em> is an abstraction that lets
|
||||
us express our code in terms of potential pausing points and eventual results
|
||||
that takes care of the details of coordination for us.</p>
|
||||
<p>This chapter builds on Chapter 16’s use of threads for parallelism and
|
||||
concurrency by introducing an alternative approach to writing code: Rust’s
|
||||
futures, streams, and the <code>async</code> and <code>await</code> syntax that let us express how
|
||||
operations could be asynchronous, and the third-party crates that implement
|
||||
asynchronous runtimes: code that manages and coordinates the execution of
|
||||
asynchronous operations.</p>
|
||||
<p>Let’s consider an example. Say you’re exporting a video you’ve created of a
|
||||
family celebration, an operation that could take anywhere from minutes to
|
||||
hours. The video export will use as much CPU and GPU power as it can. If you
|
||||
had only one CPU core and your operating system didn’t pause that export until
|
||||
it completed—that is, if it executed the export <em>synchronously</em>—you couldn’t do
|
||||
anything else on your computer while that task was running. That would be a
|
||||
pretty frustrating experience. Fortunately, your computer’s operating system
|
||||
can, and does, invisibly interrupt the export often enough to let you get other
|
||||
work done simultaneously.</p>
|
||||
<p>Now say you’re downloading a video shared by someone else, which can also take
|
||||
a while but does not take up as much CPU time. In this case, the CPU has to
|
||||
wait for data to arrive from the network. While you can start reading the data
|
||||
once it starts to arrive, it might take some time for all of it to show up.
|
||||
Even once the data is all present, if the video is quite large, it could take
|
||||
at least a second or two to load it all. That might not sound like much, but
|
||||
it’s a very long time for a modern processor, which can perform billions of
|
||||
operations every second. Again, your operating system will invisibly interrupt
|
||||
your program to allow the CPU to perform other work while waiting for the
|
||||
network call to finish.</p>
|
||||
<p>The video export is an example of a <em>CPU-bound</em> or <em>compute-bound</em> operation.
|
||||
It’s limited by the computer’s potential data processing speed within the CPU
|
||||
or GPU, and how much of that speed it can dedicate to the operation. The video
|
||||
download is an example of an <em>I/O-bound</em> operation, because it’s limited by the
|
||||
speed of the computer’s <em>input and output</em>; it can only go as fast as the data
|
||||
can be sent across the network.</p>
|
||||
<p>In both of these examples, the operating system’s invisible interrupts provide
|
||||
a form of concurrency. That concurrency happens only at the level of the entire
|
||||
program, though: the operating system interrupts one program to let other
|
||||
programs get work done. In many cases, because we understand our programs at a
|
||||
much more granular level than the operating system does, we can spot
|
||||
opportunities for concurrency that the operating system can’t see.</p>
|
||||
<p>For example, if we’re building a tool to manage file downloads, we should be
|
||||
able to write our program so that starting one download won’t lock up the UI,
|
||||
and users should be able to start multiple downloads at the same time. Many
|
||||
operating system APIs for interacting with the network are <em>blocking</em>, though;
|
||||
that is, they block the program’s progress until the data they’re processing is
|
||||
completely ready.</p>
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: This is how <em>most</em> function calls work, if you think about it. However,
|
||||
the term <em>blocking</em> is usually reserved for function calls that interact with
|
||||
files, the network, or other resources on the computer, because those are the
|
||||
cases where an individual program would benefit from the operation being
|
||||
<em>non</em>-blocking.</p>
|
||||
</section>
|
||||
<p>We could avoid blocking our main thread by spawning a dedicated thread to
|
||||
download each file. However, the overhead of the system resources used by those
|
||||
threads would eventually become a problem. It would be preferable if the call
|
||||
didn’t block in the first place, and instead we could define a number of tasks
|
||||
that we’d like our program to complete and allow the runtime to choose the best
|
||||
order and manner in which to run them.</p>
|
||||
<p>That is exactly what Rust’s <em>async</em> (short for <em>asynchronous</em>) abstraction
|
||||
gives us. In this chapter, you’ll learn all about async as we cover the
|
||||
following topics:</p>
|
||||
<ul>
|
||||
<li>How to use Rust’s <code>async</code> and <code>await</code> syntax and execute asynchronous
|
||||
functions with a runtime</li>
|
||||
<li>How to use the async model to solve some of the same challenges we looked at
|
||||
in Chapter 16</li>
|
||||
<li>How multithreading and async provide complementary solutions that you can
|
||||
combine in many cases</li>
|
||||
</ul>
|
||||
<p>Before we see how async works in practice, though, we need to take a short
|
||||
detour to discuss the differences between parallelism and concurrency.</p>
|
||||
<h2 id="parallelism-and-concurrency"><a class="header" href="#parallelism-and-concurrency">Parallelism and Concurrency</a></h2>
|
||||
<p>We’ve treated parallelism and concurrency as mostly interchangeable so far. Now
|
||||
we need to distinguish between them more precisely, because the differences
|
||||
will show up as we start working.</p>
|
||||
<p>Consider the different ways a team could split up work on a software project.
|
||||
You could assign a single member multiple tasks, assign each member one task,
|
||||
or use a mix of the two approaches.</p>
|
||||
<p>When an individual works on several different tasks before any of them is
|
||||
complete, this is <em>concurrency</em>. One way to implement concurrency is similar to
|
||||
having two different projects checked out on your computer, and when you get
|
||||
bored or stuck on one project, you switch to the other. You’re just one person,
|
||||
so you can’t make progress on both tasks at the exact same time, but you can
|
||||
multitask, making progress on one at a time by switching between them (see
|
||||
Figure 17-1).</p>
|
||||
<figure>
|
||||
<img src="../img/trpl17-01.svg" class="center" alt="A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. Arrows point from A1 to B1, B1 to A2, A2 to B2, B2 to A3, A3 to A4, and A4 to B3. The arrows between the subtasks cross the boxes between Task A and Task B." />
|
||||
<figcaption>Figure 17-1: A concurrent workflow, switching between Task A and Task B</figcaption>
|
||||
</figure>
|
||||
<p>When the team splits up a group of tasks by having each member take one task
|
||||
and work on it alone, this is <em>parallelism</em>. Each person on the team can make
|
||||
progress at the exact same time (see Figure 17-2).</p>
|
||||
<figure>
|
||||
<img src="../img/trpl17-02.svg" class="center" alt="A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. Arrows point from A1 to A2, A2 to A3, A3 to A4, B1 to B2, and B2 to B3. No arrows cross between the boxes for Task A and Task B." />
|
||||
<figcaption>Figure 17-2: A parallel workflow, where work happens on Task A and Task B independently</figcaption>
|
||||
</figure>
|
||||
<p>In both of these workflows, you might have to coordinate between different
|
||||
tasks. Maybe you thought the task assigned to one person was totally
|
||||
independent from everyone else’s work, but it actually requires another person
|
||||
on the team to finish their task first. Some of the work could be done in
|
||||
parallel, but some of it was actually <em>serial</em>: it could only happen in a
|
||||
series, one task after the other, as in Figure 17-3.</p>
|
||||
<figure>
|
||||
<img src="../img/trpl17-03.svg" class="center" alt="A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. In Task A, arrows point from A1 to A2, from A2 to a pair of thick vertical lines like a “pause” symbol, and from that symbol to A3. In task B, arrows point from B1 to B2, from B2 to B3, from B3 to A3, and from B3 to B4." />
|
||||
<figcaption>Figure 17-3: A partially parallel workflow, where work happens on Task A and Task B independently until Task A3 is blocked on the results of Task B3.</figcaption>
|
||||
</figure>
|
||||
<p>Likewise, you might realize that one of your own tasks depends on another of
|
||||
your tasks. Now your concurrent work has also become serial.</p>
|
||||
<p>Parallelism and concurrency can intersect with each other, too. If you learn
|
||||
that a colleague is stuck until you finish one of your tasks, you’ll probably
|
||||
focus all your efforts on that task to “unblock” your colleague. You and your
|
||||
coworker are no longer able to work in parallel, and you’re also no longer able
|
||||
to work concurrently on your own tasks.</p>
|
||||
<p>The same basic dynamics come into play with software and hardware. On a machine
|
||||
with a single CPU core, the CPU can perform only one operation at a time, but
|
||||
it can still work concurrently. Using tools such as threads, processes, and
|
||||
async, the computer can pause one activity and switch to others before
|
||||
eventually cycling back to that first activity again. On a machine with
|
||||
multiple CPU cores, it can also do work in parallel. One core can be performing
|
||||
one task while another core performs a completely unrelated one, and those
|
||||
operations actually happen at the same time.</p>
|
||||
<p>Running async code in Rust usually happens concurrently. Depending on the
|
||||
hardware, the operating system, and the async runtime we are using (more on
|
||||
async runtimes shortly), that concurrency may also use parallelism under the
|
||||
hood.</p>
|
||||
<p>Now, let’s dive into how async programming in Rust actually works.</p>
|
||||
</body>
|
||||
</html>
|
||||
12
ch17/ch17-00-oop.html
Normal file
12
ch17/ch17-00-oop.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to... <a href="../ch18/ch18-00-oop.html">ch18-00-oop.html</a>.</p>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
437
ch17/ch17-01-futures-and-syntax.html
Normal file
437
ch17/ch17-01-futures-and-syntax.html
Normal file
@@ -0,0 +1,437 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Futures and the Async Syntax</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="futures-and-the-async-syntax"><a class="header" href="#futures-and-the-async-syntax">Futures and the Async Syntax</a></h2>
|
||||
<p>The key elements of asynchronous programming in Rust are <em>futures</em> and Rust’s
|
||||
<code>async</code> and <code>await</code> keywords.</p>
|
||||
<p>A <em>future</em> is a value that may not be ready now but will become ready at some
|
||||
point in the future. (This same concept shows up in many languages, sometimes
|
||||
under other names such as <em>task</em> or <em>promise</em>.) Rust provides a <code>Future</code> trait
|
||||
as a building block so that different async operations can be implemented with
|
||||
different data structures but with a common interface. In Rust, futures are
|
||||
types that implement the <code>Future</code> trait. Each future holds its own information
|
||||
about the progress that has been made and what “ready” means.</p>
|
||||
<p>You can apply the <code>async</code> keyword to blocks and functions to specify that they
|
||||
can be interrupted and resumed. Within an async block or async function, you
|
||||
can use the <code>await</code> keyword to <em>await a future</em> (that is, wait for it to become
|
||||
ready). Any point where you await a future within an async block or function is
|
||||
a potential spot for that block or function to pause and resume. The process of
|
||||
checking with a future to see if its value is available yet is called <em>polling</em>.</p>
|
||||
<p>Some other languages, such as C# and JavaScript, also use <code>async</code> and <code>await</code>
|
||||
keywords for async programming. If you’re familiar with those languages, you
|
||||
may notice some significant differences in how Rust handles the syntax. That’s
|
||||
for good reason, as we’ll see!</p>
|
||||
<p>When writing async Rust, we use the <code>async</code> and <code>await</code> keywords most of the
|
||||
time. Rust compiles them into equivalent code using the <code>Future</code> trait, much as
|
||||
it compiles <code>for</code> loops into equivalent code using the <code>Iterator</code> trait.
|
||||
Because Rust provides the <code>Future</code> trait, though, you can also implement it for
|
||||
your own data types when you need to. Many of the functions we’ll see
|
||||
throughout this chapter return types with their own implementations of
|
||||
<code>Future</code>. We’ll return to the definition of the trait at the end of the chapter
|
||||
and dig into more of how it works, but this is enough detail to keep us moving
|
||||
forward.</p>
|
||||
<p>This may all feel a bit abstract, so let’s write our first async program: a
|
||||
little web scraper. We’ll pass in two URLs from the command line, fetch both of
|
||||
them concurrently, and return the result of whichever one finishes first. This
|
||||
example will have a fair bit of new syntax, but don’t worry—we’ll explain
|
||||
everything you need to know as we go.</p>
|
||||
<h2 id="our-first-async-program"><a class="header" href="#our-first-async-program">Our First Async Program</a></h2>
|
||||
<p>To keep the focus of this chapter on learning async rather than juggling parts
|
||||
of the ecosystem, we’ve created the <code>trpl</code> crate (<code>trpl</code> is short for “The Rust
|
||||
Programming Language”). It re-exports all the types, traits, and functions
|
||||
you’ll need, primarily from the <a href="https://crates.io/crates/futures"><code>futures</code></a><!-- ignore --> and
|
||||
<a href="https://tokio.rs"><code>tokio</code></a><!-- ignore --> crates. The <code>futures</code> crate is an official home
|
||||
for Rust experimentation for async code, and it’s actually where the <code>Future</code>
|
||||
trait was originally designed. Tokio is the most widely used async runtime in
|
||||
Rust today, especially for web applications. There are other great runtimes out
|
||||
there, and they may be more suitable for your purposes. We use the <code>tokio</code>
|
||||
crate under the hood for <code>trpl</code> because it’s well tested and widely used.</p>
|
||||
<p>In some cases, <code>trpl</code> also renames or wraps the original APIs to keep you
|
||||
focused on the details relevant to this chapter. If you want to understand what
|
||||
the crate does, we encourage you to check out <a href="https://github.com/rust-lang/book/tree/main/packages/trpl">its source code</a>.
|
||||
You’ll be able to see what crate each re-export comes from, and we’ve left
|
||||
extensive comments explaining what the crate does.</p>
|
||||
<p>Create a new binary project named <code>hello-async</code> and add the <code>trpl</code> crate as a
|
||||
dependency:</p>
|
||||
<pre><code class="language-console">$ cargo new hello-async
|
||||
$ cd hello-async
|
||||
$ cargo add trpl
|
||||
</code></pre>
|
||||
<p>Now we can use the various pieces provided by <code>trpl</code> to write our first async
|
||||
program. We’ll build a little command line tool that fetches two web pages,
|
||||
pulls the <code><title></code> element from each, and prints out the title of whichever
|
||||
page finishes that whole process first.</p>
|
||||
<h3 id="defining-the-page_title-function"><a class="header" href="#defining-the-page_title-function">Defining the page_title Function</a></h3>
|
||||
<p>Let’s start by writing a function that takes one page URL as a parameter, makes
|
||||
a request to it, and returns the text of the <code><title></code> element (see Listing
|
||||
17-1).</p>
|
||||
<figure class="listing" id="listing-17-1">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> // TODO: we'll add this next!
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>use trpl::Html;
|
||||
|
||||
async fn page_title(url: &str) -> Option<String> {
|
||||
let response = trpl::get(url).await;
|
||||
let response_text = response.text().await;
|
||||
Html::parse(&response_text)
|
||||
.select_first("title")
|
||||
.map(|title| title.inner_html())
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-17-1">Listing 17-1</a>: Defining an async function to get the title element from an HTML page</figcaption>
|
||||
</figure>
|
||||
<p>First, we define a function named <code>page_title</code> and mark it with the <code>async</code>
|
||||
keyword. Then we use the <code>trpl::get</code> function to fetch whatever URL is passed
|
||||
in and add the <code>await</code> keyword to await the response. To get the text of the
|
||||
<code>response</code>, we call its <code>text</code> method and once again await it with the <code>await</code>
|
||||
keyword. Both of these steps are asynchronous. For the <code>get</code> function, we have
|
||||
to wait for the server to send back the first part of its response, which will
|
||||
include HTTP headers, cookies, and so on and can be delivered separately from
|
||||
the response body. Especially if the body is very large, it can take some time
|
||||
for it all to arrive. Because we have to wait for the <em>entirety</em> of the
|
||||
response to arrive, the <code>text</code> method is also async.</p>
|
||||
<p>We have to explicitly await both of these futures, because futures in Rust are
|
||||
<em>lazy</em>: they don’t do anything until you ask them to with the <code>await</code> keyword.
|
||||
(In fact, Rust will show a compiler warning if you don’t use a future.) This
|
||||
might remind you of the discussion of iterators in the <a href="../ch13/ch13-02-iterators.html">“Processing a Series of
|
||||
Items with Iterators”</a><!-- ignore --> section in Chapter 13.
|
||||
Iterators do nothing unless you call their <code>next</code> method—whether directly or by
|
||||
using <code>for</code> loops or methods such as <code>map</code> that use <code>next</code> under the hood.
|
||||
Likewise, futures do nothing unless you explicitly ask them to. This laziness
|
||||
allows Rust to avoid running async code until it’s actually needed.</p>
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: This is different from the behavior we saw when using <code>thread::spawn</code>
|
||||
in the <a href="../ch16/ch16-01-threads.html#creating-a-new-thread-with-spawn">“Creating a New Thread with spawn”</a><!-- ignore -->
|
||||
section in Chapter 16, where the closure we passed to another thread started
|
||||
running immediately. It’s also different from how many other languages
|
||||
approach async. But it’s important for Rust to be able to provide its
|
||||
performance guarantees, just as it is with iterators.</p>
|
||||
</section>
|
||||
<p>Once we have <code>response_text</code>, we can parse it into an instance of the <code>Html</code>
|
||||
type using <code>Html::parse</code>. Instead of a raw string, we now have a data type we
|
||||
can use to work with the HTML as a richer data structure. In particular, we can
|
||||
use the <code>select_first</code> method to find the first instance of a given CSS
|
||||
selector. By passing the string <code>"title"</code>, we’ll get the first <code><title></code>
|
||||
element in the document, if there is one. Because there may not be any matching
|
||||
element, <code>select_first</code> returns an <code>Option<ElementRef></code>. Finally, we use the
|
||||
<code>Option::map</code> method, which lets us work with the item in the <code>Option</code> if it’s
|
||||
present, and do nothing if it isn’t. (We could also use a <code>match</code> expression
|
||||
here, but <code>map</code> is more idiomatic.) In the body of the function we supply to
|
||||
<code>map</code>, we call <code>inner_html</code> on the <code>title</code> to get its content, which is a
|
||||
<code>String</code>. When all is said and done, we have an <code>Option<String></code>.</p>
|
||||
<p>Notice that Rust’s <code>await</code> keyword goes <em>after</em> the expression you’re awaiting,
|
||||
not before it. That is, it’s a <em>postfix</em> keyword. This may differ from what
|
||||
you’re used to if you’ve used <code>async</code> in other languages, but in Rust it makes
|
||||
chains of methods much nicer to work with. As a result, we could change the
|
||||
body of <code>page_title</code> to chain the <code>trpl::get</code> and <code>text</code> function calls
|
||||
together with <code>await</code> between them, as shown in Listing 17-2.</p>
|
||||
<figure class="listing" id="listing-17-2">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use trpl::Html;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> // TODO: we'll add this next!
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">async fn page_title(url: &str) -> Option<String> {
|
||||
</span> let response_text = trpl::get(url).await.text().await;
|
||||
<span class="boring"> Html::parse(&response_text)
|
||||
</span><span class="boring"> .select_first("title")
|
||||
</span><span class="boring"> .map(|title| title.inner_html())
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-2">Listing 17-2</a>: Chaining with the <code>await</code> keyword</figcaption>
|
||||
</figure>
|
||||
<p>With that, we have successfully written our first async function! Before we add
|
||||
some code in <code>main</code> to call it, let’s talk a little more about what we’ve
|
||||
written and what it means.</p>
|
||||
<p>When Rust sees a <em>block</em> marked with the <code>async</code> keyword, it compiles it into a
|
||||
unique, anonymous data type that implements the <code>Future</code> trait. When Rust sees
|
||||
a <em>function</em> marked with <code>async</code>, it compiles it into a non-async function
|
||||
whose body is an async block. An async function’s return type is the type of
|
||||
the anonymous data type the compiler creates for that async block.</p>
|
||||
<p>Thus, writing <code>async fn</code> is equivalent to writing a function that returns a
|
||||
<em>future</em> of the return type. To the compiler, a function definition such as the
|
||||
<code>async fn page_title</code> in Listing 17-1 is roughly equivalent to a non-async
|
||||
function defined like this:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span>use std::future::Future;
|
||||
use trpl::Html;
|
||||
|
||||
fn page_title(url: &str) -> impl Future<Output = Option<String>> {
|
||||
async move {
|
||||
let text = trpl::get(url).await.text().await;
|
||||
Html::parse(&text)
|
||||
.select_first("title")
|
||||
.map(|title| title.inner_html())
|
||||
}
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>Let’s walk through each part of the transformed version:</p>
|
||||
<ul>
|
||||
<li>It uses the <code>impl Trait</code> syntax we discussed back in Chapter 10 in the
|
||||
<a href="../ch10/ch10-02-traits.html#traits-as-parameters">“Traits as Parameters”</a><!-- ignore --> section.</li>
|
||||
<li>The returned value implements the <code>Future</code> trait with an associated type of
|
||||
<code>Output</code>. Notice that the <code>Output</code> type is <code>Option<String></code>, which is the
|
||||
same as the original return type from the <code>async fn</code> version of <code>page_title</code>.</li>
|
||||
<li>All of the code called in the body of the original function is wrapped in
|
||||
an <code>async move</code> block. Remember that blocks are expressions. This whole block
|
||||
is the expression returned from the function.</li>
|
||||
<li>This async block produces a value with the type <code>Option<String></code>, as just
|
||||
described. That value matches the <code>Output</code> type in the return type. This is
|
||||
just like other blocks you have seen.</li>
|
||||
<li>The new function body is an <code>async move</code> block because of how it uses the
|
||||
<code>url</code> parameter. (We’ll talk much more about <code>async</code> versus <code>async move</code>
|
||||
later in the chapter.)</li>
|
||||
</ul>
|
||||
<p>Now we can call <code>page_title</code> in <code>main</code>.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="determining-a-single-pages-title"></a></p>
|
||||
<h3 id="executing-an-async-function-with-a-runtime"><a class="header" href="#executing-an-async-function-with-a-runtime">Executing an Async Function with a Runtime</a></h3>
|
||||
<p>To start, we’ll get the title for a single page, shown in Listing 17-3.
|
||||
Unfortunately, this code doesn’t compile yet.</p>
|
||||
<figure class="listing" id="listing-17-3">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore does_not_compile"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use trpl::Html;
|
||||
</span><span class="boring">
|
||||
</span>async fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let url = &args[1];
|
||||
match page_title(url).await {
|
||||
Some(title) => println!("The title for {url} was {title}"),
|
||||
None => println!("{url} had no title"),
|
||||
}
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">async fn page_title(url: &str) -> Option<String> {
|
||||
</span><span class="boring"> let response_text = trpl::get(url).await.text().await;
|
||||
</span><span class="boring"> Html::parse(&response_text)
|
||||
</span><span class="boring"> .select_first("title")
|
||||
</span><span class="boring"> .map(|title| title.inner_html())
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-3">Listing 17-3</a>: Calling the <code>page_title</code> function from <code>main</code> with a user-supplied argument</figcaption>
|
||||
</figure>
|
||||
<p>We follow the same pattern we used to get command line arguments in the
|
||||
<a href="../ch12/ch12-01-accepting-command-line-arguments.html">“Accepting Command Line Arguments”</a><!-- ignore --> section in
|
||||
Chapter 12. Then we pass the URL argument to <code>page_title</code> and await the result.
|
||||
Because the value produced by the future is an <code>Option<String></code>, we use a
|
||||
<code>match</code> expression to print different messages to account for whether the page
|
||||
had a <code><title></code>.</p>
|
||||
<p>The only place we can use the <code>await</code> keyword is in async functions or blocks,
|
||||
and Rust won’t let us mark the special <code>main</code> function as <code>async</code>.</p>
|
||||
<!-- manual-regeneration
|
||||
cd listings/ch17-async-await/listing-17-03
|
||||
cargo build
|
||||
copy just the compiler error
|
||||
-->
|
||||
<pre><code class="language-text">error[E0752]: `main` function is not allowed to be `async`
|
||||
--> src/main.rs:6:1
|
||||
|
|
||||
6 | async fn main() {
|
||||
| ^^^^^^^^^^^^^^^ `main` function is not allowed to be `async`
|
||||
</code></pre>
|
||||
<p>The reason <code>main</code> can’t be marked <code>async</code> is that async code needs a <em>runtime</em>:
|
||||
a Rust crate that manages the details of executing asynchronous code. A
|
||||
program’s <code>main</code> function can <em>initialize</em> a runtime, but it’s not a runtime
|
||||
<em>itself</em>. (We’ll see more about why this is the case in a bit.) Every Rust
|
||||
program that executes async code has at least one place where it sets up a
|
||||
runtime that executes the futures.</p>
|
||||
<p>Most languages that support async bundle a runtime, but Rust does not. Instead,
|
||||
there are many different async runtimes available, each of which makes different
|
||||
tradeoffs suitable to the use case it targets. For example, a high-throughput
|
||||
web server with many CPU cores and a large amount of RAM has very different
|
||||
needs than a microcontroller with a single core, a small amount of RAM, and no
|
||||
heap allocation ability. The crates that provide those runtimes also often
|
||||
supply async versions of common functionality such as file or network I/O.</p>
|
||||
<p>Here, and throughout the rest of this chapter, we’ll use the <code>block_on</code>
|
||||
function from the <code>trpl</code> crate, which takes a future as an argument and blocks
|
||||
the current thread until this future runs to completion. Behind the scenes,
|
||||
calling <code>block_on</code> sets up a runtime using the <code>tokio</code> crate that’s used to run
|
||||
the future passed in (the <code>trpl</code> crate’s <code>block_on</code> behavior is similar to
|
||||
other runtime crates’ <code>block_on</code> functions). Once the future completes,
|
||||
<code>block_on</code> returns whatever value the future produced.</p>
|
||||
<p>We could pass the future returned by <code>page_title</code> directly to <code>block_on</code> and,
|
||||
once it completed, we could match on the resulting <code>Option<String></code> as we tried
|
||||
to do in Listing 17-3. However, for most of the examples in the chapter (and
|
||||
most async code in the real world), we’ll be doing more than just one async
|
||||
function call, so instead we’ll pass an <code>async</code> block and explicitly await the
|
||||
result of the <code>page_title</code> call, as in Listing 17-4.</p>
|
||||
<figure class="listing" id="listing-17-4">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<!-- should_panic,noplayground because mdbook test does not pass args -->
|
||||
<pre><code class="language-rust should_panic noplayground"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use trpl::Html;
|
||||
</span><span class="boring">
|
||||
</span>fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
trpl::block_on(async {
|
||||
let url = &args[1];
|
||||
match page_title(url).await {
|
||||
Some(title) => println!("The title for {url} was {title}"),
|
||||
None => println!("{url} had no title"),
|
||||
}
|
||||
})
|
||||
}
|
||||
<span class="boring">
|
||||
</span><span class="boring">async fn page_title(url: &str) -> Option<String> {
|
||||
</span><span class="boring"> let response_text = trpl::get(url).await.text().await;
|
||||
</span><span class="boring"> Html::parse(&response_text)
|
||||
</span><span class="boring"> .select_first("title")
|
||||
</span><span class="boring"> .map(|title| title.inner_html())
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-4">Listing 17-4</a>: Awaiting an async block with <code>trpl::block_on</code></figcaption>
|
||||
</figure>
|
||||
<p>When we run this code, we get the behavior we expected initially:</p>
|
||||
<!-- manual-regeneration
|
||||
cd listings/ch17-async-await/listing-17-04
|
||||
cargo build # skip all the build noise
|
||||
cargo run -- "https://www.rust-lang.org"
|
||||
# copy the output here
|
||||
-->
|
||||
<pre><code class="language-console">$ cargo run -- "https://www.rust-lang.org"
|
||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
|
||||
Running `target/debug/async_await 'https://www.rust-lang.org'`
|
||||
The title for https://www.rust-lang.org was
|
||||
Rust Programming Language
|
||||
</code></pre>
|
||||
<p>Phew—we finally have some working async code! But before we add the code to
|
||||
race two sites against each other, let’s briefly turn our attention back to how
|
||||
futures work.</p>
|
||||
<p>Each <em>await point</em>—that is, every place where the code uses the <code>await</code>
|
||||
keyword—represents a place where control is handed back to the runtime. To make
|
||||
that work, Rust needs to keep track of the state involved in the async block so
|
||||
that the runtime could kick off some other work and then come back when it’s
|
||||
ready to try advancing the first one again. This is an invisible state machine,
|
||||
as if you’d written an enum like this to save the current state at each await
|
||||
point:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span>enum PageTitleFuture<'a> {
|
||||
Initial { url: &'a str },
|
||||
GetAwaitPoint { url: &'a str },
|
||||
TextAwaitPoint { response: trpl::Response },
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>Writing the code to transition between each state by hand would be tedious and
|
||||
error-prone, however, especially when you need to add more functionality and
|
||||
more states to the code later. Fortunately, the Rust compiler creates and
|
||||
manages the state machine data structures for async code automatically. The
|
||||
normal borrowing and ownership rules around data structures all still apply,
|
||||
and happily, the compiler also handles checking those for us and provides
|
||||
useful error messages. We’ll work through a few of those later in the chapter.</p>
|
||||
<p>Ultimately, something has to execute this state machine, and that something is
|
||||
a runtime. (This is why you may come across mentions of <em>executors</em> when
|
||||
looking into runtimes: an executor is the part of a runtime responsible for
|
||||
executing the async code.)</p>
|
||||
<p>Now you can see why the compiler stopped us from making <code>main</code> itself an async
|
||||
function back in Listing 17-3. If <code>main</code> were an async function, something else
|
||||
would need to manage the state machine for whatever future <code>main</code> returned, but
|
||||
<code>main</code> is the starting point for the program! Instead, we called the
|
||||
<code>trpl::block_on</code> function in <code>main</code> to set up a runtime and run the future
|
||||
returned by the <code>async</code> block until it’s done.</p>
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: Some runtimes provide macros so you <em>can</em> write an async <code>main</code>
|
||||
function. Those macros rewrite <code>async fn main() { ... }</code> to be a normal <code>fn main</code>, which does the same thing we did by hand in Listing 17-4: call a
|
||||
function that runs a future to completion the way <code>trpl::block_on</code> does.</p>
|
||||
</section>
|
||||
<p>Now let’s put these pieces together and see how we can write concurrent code.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="racing-our-two-urls-against-each-other"></a></p>
|
||||
<h3 id="racing-two-urls-against-each-other-concurrently"><a class="header" href="#racing-two-urls-against-each-other-concurrently">Racing Two URLs Against Each Other Concurrently</a></h3>
|
||||
<p>In Listing 17-5, we call <code>page_title</code> with two different URLs passed in from the
|
||||
command line and race them by selecting whichever future finishes first.</p>
|
||||
<figure class="listing" id="listing-17-5">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<!-- should_panic,noplayground because mdbook does not pass args -->
|
||||
<pre><code class="language-rust should_panic noplayground"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span>use trpl::{Either, Html};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
trpl::block_on(async {
|
||||
let title_fut_1 = page_title(&args[1]);
|
||||
let title_fut_2 = page_title(&args[2]);
|
||||
|
||||
let (url, maybe_title) =
|
||||
match trpl::select(title_fut_1, title_fut_2).await {
|
||||
Either::Left(left) => left,
|
||||
Either::Right(right) => right,
|
||||
};
|
||||
|
||||
println!("{url} returned first");
|
||||
match maybe_title {
|
||||
Some(title) => println!("Its page title was: '{title}'"),
|
||||
None => println!("It had no title."),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn page_title(url: &str) -> (&str, Option<String>) {
|
||||
let response_text = trpl::get(url).await.text().await;
|
||||
let title = Html::parse(&response_text)
|
||||
.select_first("title")
|
||||
.map(|title| title.inner_html());
|
||||
(url, title)
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-17-5">Listing 17-5</a>: Calling <code>page_title</code> for two URLs to see which returns first</figcaption>
|
||||
</figure>
|
||||
<p>We begin by calling <code>page_title</code> for each of the user-supplied URLs. We save
|
||||
the resulting futures as <code>title_fut_1</code> and <code>title_fut_2</code>. Remember, these don’t
|
||||
do anything yet, because futures are lazy and we haven’t yet awaited them. Then
|
||||
we pass the futures to <code>trpl::select</code>, which returns a value to indicate which
|
||||
of the futures passed to it finishes first.</p>
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: Under the hood, <code>trpl::select</code> is built on a more general <code>select</code>
|
||||
function defined in the <code>futures</code> crate. The <code>futures</code> crate’s <code>select</code>
|
||||
function can do a lot of things that the <code>trpl::select</code> function can’t, but
|
||||
it also has some additional complexity that we can skip over for now.</p>
|
||||
</section>
|
||||
<p>Either future can legitimately “win,” so it doesn’t make sense to return a
|
||||
<code>Result</code>. Instead, <code>trpl::select</code> returns a type we haven’t seen before,
|
||||
<code>trpl::Either</code>. The <code>Either</code> type is somewhat similar to a <code>Result</code> in that it
|
||||
has two cases. Unlike <code>Result</code>, though, there is no notion of success or
|
||||
failure baked into <code>Either</code>. Instead, it uses <code>Left</code> and <code>Right</code> to indicate
|
||||
“one or the other”:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>enum Either<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>The <code>select</code> function returns <code>Left</code> with that future’s output if the first
|
||||
argument wins, and <code>Right</code> with the second future argument’s output if <em>that</em>
|
||||
one wins. This matches the order the arguments appear in when calling the
|
||||
function: the first argument is to the left of the second argument.</p>
|
||||
<p>We also update <code>page_title</code> to return the same URL passed in. That way, if the
|
||||
page that returns first does not have a <code><title></code> we can resolve, we can still
|
||||
print a meaningful message. With that information available, we wrap up by
|
||||
updating our <code>println!</code> output to indicate both which URL finished first and
|
||||
what, if any, the <code><title></code> is for the web page at that URL.</p>
|
||||
<p>You have built a small working web scraper now! Pick a couple URLs and run the
|
||||
command line tool. You may discover that some sites are consistently faster
|
||||
than others, while in other cases the faster site varies from run to run. More
|
||||
importantly, you’ve learned the basics of working with futures, so now we can
|
||||
dig deeper into what we can do with async.</p>
|
||||
<!-- TODO: map source link version to version of Rust? -->
|
||||
</body>
|
||||
</html>
|
||||
12
ch17/ch17-01-what-is-oo.html
Normal file
12
ch17/ch17-01-what-is-oo.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to... <a href="../ch18/ch18-01-what-is-oo.html">ch18-01-what-is-oo.html</a>.</p>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
540
ch17/ch17-02-concurrency-with-async.html
Normal file
540
ch17/ch17-02-concurrency-with-async.html
Normal file
@@ -0,0 +1,540 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Applying Concurrency with Async</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="concurrency-with-async"></a></p>
|
||||
<h2 id="applying-concurrency-with-async"><a class="header" href="#applying-concurrency-with-async">Applying Concurrency with Async</a></h2>
|
||||
<p>In this section, we’ll apply async to some of the same concurrency challenges
|
||||
we tackled with threads in Chapter 16. Because we already talked about a lot of
|
||||
the key ideas there, in this section we’ll focus on what’s different between
|
||||
threads and futures.</p>
|
||||
<p>In many cases, the APIs for working with concurrency using async are very
|
||||
similar to those for using threads. In other cases, they end up being quite
|
||||
different. Even when the APIs <em>look</em> similar between threads and async, they
|
||||
often have different behavior—and they nearly always have different performance
|
||||
characteristics.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="counting"></a></p>
|
||||
<h3 id="creating-a-new-task-with-spawn_task"><a class="header" href="#creating-a-new-task-with-spawn_task">Creating a New Task with <code>spawn_task</code></a></h3>
|
||||
<p>The first operation we tackled in the <a href="../ch16/ch16-01-threads.html#creating-a-new-thread-with-spawn">“Creating a New Thread with
|
||||
<code>spawn</code>”</a><!-- ignore --> section in Chapter 16 was counting up on
|
||||
two separate threads. Let’s do the same using async. The <code>trpl</code> crate supplies
|
||||
a <code>spawn_task</code> function that looks very similar to the <code>thread::spawn</code> API, and
|
||||
a <code>sleep</code> function that is an async version of the <code>thread::sleep</code> API. We can
|
||||
use these together to implement the counting example, as shown in Listing 17-6.</p>
|
||||
<figure class="listing" id="listing-17-6">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span>use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
trpl::block_on(async {
|
||||
trpl::spawn_task(async {
|
||||
for i in 1..10 {
|
||||
println!("hi number {i} from the first task!");
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
});
|
||||
|
||||
for i in 1..5 {
|
||||
println!("hi number {i} from the second task!");
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
});
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-17-6">Listing 17-6</a>: Creating a new task to print one thing while the main task prints something else</figcaption>
|
||||
</figure>
|
||||
<p>As our starting point, we set up our <code>main</code> function with <code>trpl::block_on</code> so
|
||||
that our top-level function can be async.</p>
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: From this point forward in the chapter, every example will include this
|
||||
exact same wrapping code with <code>trpl::block_on</code> in <code>main</code>, so we’ll often skip it
|
||||
just as we do with <code>main</code>. Remember to include it in your code!</p>
|
||||
</section>
|
||||
<p>Then we write two loops within that block, each containing a <code>trpl::sleep</code>
|
||||
call, which waits for half a second (500 milliseconds) before sending the next
|
||||
message. We put one loop in the body of a <code>trpl::spawn_task</code> and the other in a
|
||||
top-level <code>for</code> loop. We also add an <code>await</code> after the <code>sleep</code> calls.</p>
|
||||
<p>This code behaves similarly to the thread-based implementation—including the
|
||||
fact that you may see the messages appear in a different order in your own
|
||||
terminal when you run it:</p>
|
||||
<!-- Not extracting output because changes to this output aren't significant;
|
||||
the changes are likely to be due to the threads running differently rather than
|
||||
changes in the compiler -->
|
||||
<pre><code class="language-text">hi number 1 from the second task!
|
||||
hi number 1 from the first task!
|
||||
hi number 2 from the first task!
|
||||
hi number 2 from the second task!
|
||||
hi number 3 from the first task!
|
||||
hi number 3 from the second task!
|
||||
hi number 4 from the first task!
|
||||
hi number 4 from the second task!
|
||||
hi number 5 from the first task!
|
||||
</code></pre>
|
||||
<p>This version stops as soon as the <code>for</code> loop in the body of the main async
|
||||
block finishes, because the task spawned by <code>spawn_task</code> is shut down when the
|
||||
<code>main</code> function ends. If you want it to run all the way to the task’s
|
||||
completion, you will need to use a join handle to wait for the first task to
|
||||
complete. With threads, we used the <code>join</code> method to “block” until the thread
|
||||
was done running. In Listing 17-7, we can use <code>await</code> to do the same thing,
|
||||
because the task handle itself is a future. Its <code>Output</code> type is a <code>Result</code>, so
|
||||
we also unwrap it after awaiting it.</p>
|
||||
<figure class="listing" id="listing-17-7">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let handle = trpl::spawn_task(async {
|
||||
for i in 1..10 {
|
||||
println!("hi number {i} from the first task!");
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
});
|
||||
|
||||
for i in 1..5 {
|
||||
println!("hi number {i} from the second task!");
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
handle.await.unwrap();
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-7">Listing 17-7</a>: Using <code>await</code> with a join handle to run a task to completion</figcaption>
|
||||
</figure>
|
||||
<p>This updated version runs until <em>both</em> loops finish:</p>
|
||||
<!-- Not extracting output because changes to this output aren't significant;
|
||||
the changes are likely to be due to the threads running differently rather than
|
||||
changes in the compiler -->
|
||||
<pre><code class="language-text">hi number 1 from the second task!
|
||||
hi number 1 from the first task!
|
||||
hi number 2 from the first task!
|
||||
hi number 2 from the second task!
|
||||
hi number 3 from the first task!
|
||||
hi number 3 from the second task!
|
||||
hi number 4 from the first task!
|
||||
hi number 4 from the second task!
|
||||
hi number 5 from the first task!
|
||||
hi number 6 from the first task!
|
||||
hi number 7 from the first task!
|
||||
hi number 8 from the first task!
|
||||
hi number 9 from the first task!
|
||||
</code></pre>
|
||||
<p>So far, it looks like async and threads give us similar outcomes, just with
|
||||
different syntax: using <code>await</code> instead of calling <code>join</code> on the join handle,
|
||||
and awaiting the <code>sleep</code> calls.</p>
|
||||
<p>The bigger difference is that we didn’t need to spawn another operating system
|
||||
thread to do this. In fact, we don’t even need to spawn a task here. Because
|
||||
async blocks compile to anonymous futures, we can put each loop in an async
|
||||
block and have the runtime run them both to completion using the <code>trpl::join</code>
|
||||
function.</p>
|
||||
<p>In the <a href="../ch16/ch16-01-threads.html#waiting-for-all-threads-to-finish">“Waiting for All Threads to Finish”</a><!-- ignore -->
|
||||
section in Chapter 16, we showed how to use the <code>join</code> method on the
|
||||
<code>JoinHandle</code> type returned when you call <code>std::thread::spawn</code>. The <code>trpl::join</code>
|
||||
function is similar, but for futures. When you give it two futures, it produces
|
||||
a single new future whose output is a tuple containing the output of each
|
||||
future you passed in once they <em>both</em> complete. Thus, in Listing 17-8, we use
|
||||
<code>trpl::join</code> to wait for both <code>fut1</code> and <code>fut2</code> to finish. We do <em>not</em> await
|
||||
<code>fut1</code> and <code>fut2</code> but instead the new future produced by <code>trpl::join</code>. We
|
||||
ignore the output, because it’s just a tuple containing two unit values.</p>
|
||||
<figure class="listing" id="listing-17-8">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let fut1 = async {
|
||||
for i in 1..10 {
|
||||
println!("hi number {i} from the first task!");
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
};
|
||||
|
||||
let fut2 = async {
|
||||
for i in 1..5 {
|
||||
println!("hi number {i} from the second task!");
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
};
|
||||
|
||||
trpl::join(fut1, fut2).await;
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-8">Listing 17-8</a>: Using <code>trpl::join</code> to await two anonymous futures</figcaption>
|
||||
</figure>
|
||||
<p>When we run this, we see both futures run to completion:</p>
|
||||
<!-- Not extracting output because changes to this output aren't significant;
|
||||
the changes are likely to be due to the threads running differently rather than
|
||||
changes in the compiler -->
|
||||
<pre><code class="language-text">hi number 1 from the first task!
|
||||
hi number 1 from the second task!
|
||||
hi number 2 from the first task!
|
||||
hi number 2 from the second task!
|
||||
hi number 3 from the first task!
|
||||
hi number 3 from the second task!
|
||||
hi number 4 from the first task!
|
||||
hi number 4 from the second task!
|
||||
hi number 5 from the first task!
|
||||
hi number 6 from the first task!
|
||||
hi number 7 from the first task!
|
||||
hi number 8 from the first task!
|
||||
hi number 9 from the first task!
|
||||
</code></pre>
|
||||
<p>Now, you’ll see the exact same order every time, which is very different from
|
||||
what we saw with threads and with <code>trpl::spawn_task</code> in Listing 17-7. That is
|
||||
because the <code>trpl::join</code> function is <em>fair</em>, meaning it checks each future
|
||||
equally often, alternating between them, and never lets one race ahead if the
|
||||
other is ready. With threads, the operating system decides which thread to
|
||||
check and how long to let it run. With async Rust, the runtime decides which
|
||||
task to check. (In practice, the details get complicated because an async
|
||||
runtime might use operating system threads under the hood as part of how it
|
||||
manages concurrency, so guaranteeing fairness can be more work for a
|
||||
runtime—but it’s still possible!) Runtimes don’t have to guarantee fairness for
|
||||
any given operation, and they often offer different APIs to let you choose
|
||||
whether or not you want fairness.</p>
|
||||
<p>Try some of these variations on awaiting the futures and see what they do:</p>
|
||||
<ul>
|
||||
<li>Remove the async block from around either or both of the loops.</li>
|
||||
<li>Await each async block immediately after defining it.</li>
|
||||
<li>Wrap only the first loop in an async block, and await the resulting future
|
||||
after the body of second loop.</li>
|
||||
</ul>
|
||||
<p>For an extra challenge, see if you can figure out what the output will be in
|
||||
each case <em>before</em> running the code!</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="message-passing"></a>
|
||||
<a id="counting-up-on-two-tasks-using-message-passing"></a></p>
|
||||
<h3 id="sending-data-between-two-tasks-using-message-passing"><a class="header" href="#sending-data-between-two-tasks-using-message-passing">Sending Data Between Two Tasks Using Message Passing</a></h3>
|
||||
<p>Sharing data between futures will also be familiar: we’ll use message passing
|
||||
again, but this time with async versions of the types and functions. We’ll take
|
||||
a slightly different path than we did in the <a href="../ch16/ch16-02-message-passing.html">“Transfer Data Between Threads
|
||||
with Message Passing”</a><!-- ignore --> section in
|
||||
Chapter 16 to illustrate some of the key differences between thread-based and
|
||||
futures-based concurrency. In Listing 17-9, we’ll begin with just a single
|
||||
async block—<em>not</em> spawning a separate task as we spawned a separate thread.</p>
|
||||
<figure class="listing" id="listing-17-9">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let (tx, mut rx) = trpl::channel();
|
||||
|
||||
let val = String::from("hi");
|
||||
tx.send(val).unwrap();
|
||||
|
||||
let received = rx.recv().await.unwrap();
|
||||
println!("received '{received}'");
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-9">Listing 17-9</a>: Creating an async channel and assigning the two halves to <code>tx</code> and <code>rx</code></figcaption>
|
||||
</figure>
|
||||
<p>Here, we use <code>trpl::channel</code>, an async version of the multiple-producer,
|
||||
single-consumer channel API we used with threads back in Chapter 16. The async
|
||||
version of the API is only a little different from the thread-based version: it
|
||||
uses a mutable rather than an immutable receiver <code>rx</code>, and its <code>recv</code> method
|
||||
produces a future we need to await rather than producing the value directly.
|
||||
Now we can send messages from the sender to the receiver. Notice that we don’t
|
||||
have to spawn a separate thread or even a task; we merely need to await the
|
||||
<code>rx.recv</code> call.</p>
|
||||
<p>The synchronous <code>Receiver::recv</code> method in <code>std::mpsc::channel</code> blocks until it
|
||||
receives a message. The <code>trpl::Receiver::recv</code> method does not, because it is
|
||||
async. Instead of blocking, it hands control back to the runtime until either a
|
||||
message is received or the send side of the channel closes. By contrast, we
|
||||
don’t await the <code>send</code> call, because it doesn’t block. It doesn’t need to,
|
||||
because the channel we’re sending it into is unbounded.</p>
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: Because all of this async code runs in an async block in a
|
||||
<code>trpl::block_on</code> call, everything within it can avoid blocking. However, the
|
||||
code <em>outside</em> it will block on the <code>block_on</code> function returning. That’s the
|
||||
whole point of the <code>trpl::block_on</code> function: it lets you <em>choose</em> where to
|
||||
block on some set of async code, and thus where to transition between sync
|
||||
and async code.</p>
|
||||
</section>
|
||||
<p>Notice two things about this example. First, the message will arrive right
|
||||
away. Second, although we use a future here, there’s no concurrency yet.
|
||||
Everything in the listing happens in sequence, just as it would if there were
|
||||
no futures involved.</p>
|
||||
<p>Let’s address the first part by sending a series of messages and sleeping in
|
||||
between them, as shown in Listing 17-10.</p>
|
||||
<!-- We cannot test this one because it never stops! -->
|
||||
<figure class="listing" id="listing-17-10">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let (tx, mut rx) = trpl::channel();
|
||||
|
||||
let vals = vec![
|
||||
String::from("hi"),
|
||||
String::from("from"),
|
||||
String::from("the"),
|
||||
String::from("future"),
|
||||
];
|
||||
|
||||
for val in vals {
|
||||
tx.send(val).unwrap();
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
while let Some(value) = rx.recv().await {
|
||||
println!("received '{value}'");
|
||||
}
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-10">Listing 17-10</a>: Sending and receiving multiple messages over the async channel and sleeping with an <code>await</code> between each message</figcaption>
|
||||
</figure>
|
||||
<p>In addition to sending the messages, we need to receive them. In this case,
|
||||
because we know how many messages are coming in, we could do that manually by
|
||||
calling <code>rx.recv().await</code> four times. In the real world, though, we’ll generally
|
||||
be waiting on some <em>unknown</em> number of messages, so we need to keep waiting
|
||||
until we determine that there are no more messages.</p>
|
||||
<p>In Listing 16-10, we used a <code>for</code> loop to process all the items received from a
|
||||
synchronous channel. Rust doesn’t yet have a way to use a <code>for</code> loop with an
|
||||
<em>asynchronously produced</em> series of items, however, so we need to use a loop we
|
||||
haven’t seen before: the <code>while let</code> conditional loop. This is the loop version
|
||||
of the <code>if let</code> construct we saw back in the <a href="../ch06/ch06-03-if-let.html">“Concise Control Flow with <code>if let</code> and <code>let...else</code>”</a><!-- ignore --> section in Chapter 6. The loop
|
||||
will continue executing as long as the pattern it specifies continues to match
|
||||
the value.</p>
|
||||
<p>The <code>rx.recv</code> call produces a future, which we await. The runtime will pause
|
||||
the future until it is ready. Once a message arrives, the future will resolve
|
||||
to <code>Some(message)</code> as many times as a message arrives. When the channel closes,
|
||||
regardless of whether <em>any</em> messages have arrived, the future will instead
|
||||
resolve to <code>None</code> to indicate that there are no more values and thus we should
|
||||
stop polling—that is, stop awaiting.</p>
|
||||
<p>The <code>while let</code> loop pulls all of this together. If the result of calling
|
||||
<code>rx.recv().await</code> is <code>Some(message)</code>, we get access to the message and we can
|
||||
use it in the loop body, just as we could with <code>if let</code>. If the result is
|
||||
<code>None</code>, the loop ends. Every time the loop completes, it hits the await point
|
||||
again, so the runtime pauses it again until another message arrives.</p>
|
||||
<p>The code now successfully sends and receives all of the messages.
|
||||
Unfortunately, there are still a couple of problems. For one thing, the
|
||||
messages do not arrive at half-second intervals. They arrive all at once, 2
|
||||
seconds (2,000 milliseconds) after we start the program. For another, this
|
||||
program also never exits! Instead, it waits forever for new messages. You will
|
||||
need to shut it down using <kbd>ctrl</kbd>-<kbd>C</kbd>.</p>
|
||||
<h4 id="code-within-one-async-block-executes-linearly"><a class="header" href="#code-within-one-async-block-executes-linearly">Code Within One Async Block Executes Linearly</a></h4>
|
||||
<p>Let’s start by examining why the messages come in all at once after the full
|
||||
delay, rather than coming in with delays between each one. Within a given async
|
||||
block, the order in which <code>await</code> keywords appear in the code is also the order
|
||||
in which they’re executed when the program runs.</p>
|
||||
<p>There’s only one async block in Listing 17-10, so everything in it runs
|
||||
linearly. There’s still no concurrency. All the <code>tx.send</code> calls happen,
|
||||
interspersed with all of the <code>trpl::sleep</code> calls and their associated await
|
||||
points. Only then does the <code>while let</code> loop get to go through any of the
|
||||
<code>await</code> points on the <code>recv</code> calls.</p>
|
||||
<p>To get the behavior we want, where the sleep delay happens between each
|
||||
message, we need to put the <code>tx</code> and <code>rx</code> operations in their own async blocks,
|
||||
as shown in Listing 17-11. Then the runtime can execute each of them separately
|
||||
using <code>trpl::join</code>, just as in Listing 17-8. Once again, we await the result of
|
||||
calling <code>trpl::join</code>, not the individual futures. If we awaited the individual
|
||||
futures in sequence, we would just end up back in a sequential flow—exactly
|
||||
what we’re trying <em>not</em> to do.</p>
|
||||
<!-- We cannot test this one because it never stops! -->
|
||||
<figure class="listing" id="listing-17-11">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span><span class="boring"> let (tx, mut rx) = trpl::channel();
|
||||
</span><span class="boring">
|
||||
</span> let tx_fut = async {
|
||||
let vals = vec![
|
||||
String::from("hi"),
|
||||
String::from("from"),
|
||||
String::from("the"),
|
||||
String::from("future"),
|
||||
];
|
||||
|
||||
for val in vals {
|
||||
tx.send(val).unwrap();
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
};
|
||||
|
||||
let rx_fut = async {
|
||||
while let Some(value) = rx.recv().await {
|
||||
println!("received '{value}'");
|
||||
}
|
||||
};
|
||||
|
||||
trpl::join(tx_fut, rx_fut).await;
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-11">Listing 17-11</a>: Separating <code>send</code> and <code>recv</code> into their own <code>async</code> blocks and awaiting the futures for those blocks</figcaption>
|
||||
</figure>
|
||||
<p>With the updated code in Listing 17-11, the messages get printed at
|
||||
500-millisecond intervals, rather than all in a rush after 2 seconds.</p>
|
||||
<h4 id="moving-ownership-into-an-async-block"><a class="header" href="#moving-ownership-into-an-async-block">Moving Ownership Into an Async Block</a></h4>
|
||||
<p>The program still never exits, though, because of the way the <code>while let</code> loop
|
||||
interacts with <code>trpl::join</code>:</p>
|
||||
<ul>
|
||||
<li>The future returned from <code>trpl::join</code> completes only once <em>both</em> futures
|
||||
passed to it have completed.</li>
|
||||
<li>The <code>tx_fut</code> future completes once it finishes sleeping after sending the last
|
||||
message in <code>vals</code>.</li>
|
||||
<li>The <code>rx_fut</code> future won’t complete until the <code>while let</code> loop ends.</li>
|
||||
<li>The <code>while let</code> loop won’t end until awaiting <code>rx.recv</code> produces <code>None</code>.</li>
|
||||
<li>Awaiting <code>rx.recv</code> will return <code>None</code> only once the other end of the channel
|
||||
is closed.</li>
|
||||
<li>The channel will close only if we call <code>rx.close</code> or when the sender side,
|
||||
<code>tx</code>, is dropped.</li>
|
||||
<li>We don’t call <code>rx.close</code> anywhere, and <code>tx</code> won’t be dropped until the
|
||||
outermost async block passed to <code>trpl::block_on</code> ends.</li>
|
||||
<li>The block can’t end because it is blocked on <code>trpl::join</code> completing, which
|
||||
takes us back to the top of this list.</li>
|
||||
</ul>
|
||||
<p>Right now, the async block where we send the messages only <em>borrows</em> <code>tx</code>
|
||||
because sending a message doesn’t require ownership, but if we could <em>move</em>
|
||||
<code>tx</code> into that async block, it would be dropped once that block ends. In the
|
||||
<a href="../ch13/ch13-01-closures.html#capturing-references-or-moving-ownership">“Capturing References or Moving Ownership”</a><!-- ignore -->
|
||||
section in Chapter 13, you learned how to use the <code>move</code> keyword with closures,
|
||||
and, as discussed in the <a href="../ch16/ch16-01-threads.html#using-move-closures-with-threads">“Using <code>move</code> Closures with
|
||||
Threads”</a><!-- ignore --> section in Chapter 16, we often need to
|
||||
move data into closures when working with threads. The same basic dynamics
|
||||
apply to async blocks, so the <code>move</code> keyword works with async blocks just as it
|
||||
does with closures.</p>
|
||||
<p>In Listing 17-12, we change the block used to send messages from <code>async</code> to
|
||||
<code>async move</code>.</p>
|
||||
<figure class="listing" id="listing-17-12">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let (tx, mut rx) = trpl::channel();
|
||||
|
||||
let tx_fut = async move {
|
||||
// --snip--
|
||||
<span class="boring"> let vals = vec![
|
||||
</span><span class="boring"> String::from("hi"),
|
||||
</span><span class="boring"> String::from("from"),
|
||||
</span><span class="boring"> String::from("the"),
|
||||
</span><span class="boring"> String::from("future"),
|
||||
</span><span class="boring"> ];
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for val in vals {
|
||||
</span><span class="boring"> tx.send(val).unwrap();
|
||||
</span><span class="boring"> trpl::sleep(Duration::from_millis(500)).await;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let rx_fut = async {
|
||||
</span><span class="boring"> while let Some(value) = rx.recv().await {
|
||||
</span><span class="boring"> println!("received '{value}'");
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> trpl::join(tx_fut, rx_fut).await;
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-12">Listing 17-12</a>: A revision of the code from Listing 17-11 that correctly shuts down when complete</figcaption>
|
||||
</figure>
|
||||
<p>When we run <em>this</em> version of the code, it shuts down gracefully after the last
|
||||
message is sent and received. Next, let’s see what would need to change to send
|
||||
data from more than one future.</p>
|
||||
<h4 id="joining-a-number-of-futures-with-the-join-macro"><a class="header" href="#joining-a-number-of-futures-with-the-join-macro">Joining a Number of Futures with the <code>join!</code> Macro</a></h4>
|
||||
<p>This async channel is also a multiple-producer channel, so we can call <code>clone</code>
|
||||
on <code>tx</code> if we want to send messages from multiple futures, as shown in Listing
|
||||
17-13.</p>
|
||||
<figure class="listing" id="listing-17-13">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let (tx, mut rx) = trpl::channel();
|
||||
|
||||
let tx1 = tx.clone();
|
||||
let tx1_fut = async move {
|
||||
let vals = vec![
|
||||
String::from("hi"),
|
||||
String::from("from"),
|
||||
String::from("the"),
|
||||
String::from("future"),
|
||||
];
|
||||
|
||||
for val in vals {
|
||||
tx1.send(val).unwrap();
|
||||
trpl::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
};
|
||||
|
||||
let rx_fut = async {
|
||||
while let Some(value) = rx.recv().await {
|
||||
println!("received '{value}'");
|
||||
}
|
||||
};
|
||||
|
||||
let tx_fut = async move {
|
||||
let vals = vec![
|
||||
String::from("more"),
|
||||
String::from("messages"),
|
||||
String::from("for"),
|
||||
String::from("you"),
|
||||
];
|
||||
|
||||
for val in vals {
|
||||
tx.send(val).unwrap();
|
||||
trpl::sleep(Duration::from_millis(1500)).await;
|
||||
}
|
||||
};
|
||||
|
||||
trpl::join!(tx1_fut, tx_fut, rx_fut);
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-13">Listing 17-13</a>: Using multiple producers with async blocks</figcaption>
|
||||
</figure>
|
||||
<p>First, we clone <code>tx</code>, creating <code>tx1</code> outside the first async block. We move
|
||||
<code>tx1</code> into that block just as we did before with <code>tx</code>. Then, later, we move the
|
||||
original <code>tx</code> into a <em>new</em> async block, where we send more messages on a
|
||||
slightly slower delay. We happen to put this new async block after the async
|
||||
block for receiving messages, but it could go before it just as well. The key is
|
||||
the order in which the futures are awaited, not in which they’re created.</p>
|
||||
<p>Both of the async blocks for sending messages need to be <code>async move</code> blocks so
|
||||
that both <code>tx</code> and <code>tx1</code> get dropped when those blocks finish. Otherwise, we’ll
|
||||
end up back in the same infinite loop we started out in.</p>
|
||||
<p>Finally, we switch from <code>trpl::join</code> to <code>trpl::join!</code> to handle the additional
|
||||
future: the <code>join!</code> macro awaits an arbitrary number of futures where we know
|
||||
the number of futures at compile time. We’ll discuss awaiting a collection of
|
||||
an unknown number of futures later in this chapter.</p>
|
||||
<p>Now we see all the messages from both sending futures, and because the sending
|
||||
futures use slightly different delays after sending, the messages are also
|
||||
received at those different intervals:</p>
|
||||
<!-- Not extracting output because changes to this output aren't significant;
|
||||
the changes are likely to be due to the threads running differently rather than
|
||||
changes in the compiler -->
|
||||
<pre><code class="language-text">received 'hi'
|
||||
received 'more'
|
||||
received 'from'
|
||||
received 'the'
|
||||
received 'messages'
|
||||
received 'future'
|
||||
received 'for'
|
||||
received 'you'
|
||||
</code></pre>
|
||||
<p>We’ve explored how to use message passing to send data between futures, how
|
||||
code within an async block runs sequentially, how to move ownership into an
|
||||
async block, and how to join multiple futures. Next, let’s discuss how and why
|
||||
to tell the runtime it can switch to another task.</p>
|
||||
</body>
|
||||
</html>
|
||||
12
ch17/ch17-02-trait-objects.html
Normal file
12
ch17/ch17-02-trait-objects.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to... <a href="../ch18/ch18-02-trait-objects.html">ch18-02-trait-objects.html</a>.</p>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
393
ch17/ch17-03-more-futures.html
Normal file
393
ch17/ch17-03-more-futures.html
Normal file
@@ -0,0 +1,393 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Working With Any Number of Futures</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="yielding"></a></p>
|
||||
<h3 id="yielding-control-to-the-runtime"><a class="header" href="#yielding-control-to-the-runtime">Yielding Control to the Runtime</a></h3>
|
||||
<p>Recall from the <a href="ch17-01-futures-and-syntax.html#our-first-async-program">“Our First Async Program”</a><!-- ignore -->
|
||||
section that at each await point, Rust gives a runtime a chance to pause the
|
||||
task and switch to another one if the future being awaited isn’t ready. The
|
||||
inverse is also true: Rust <em>only</em> pauses async blocks and hands control back to
|
||||
a runtime at an await point. Everything between await points is synchronous.</p>
|
||||
<p>That means if you do a bunch of work in an async block without an await point,
|
||||
that future will block any other futures from making progress. You may sometimes
|
||||
hear this referred to as one future <em>starving</em> other futures. In some cases,
|
||||
that may not be a big deal. However, if you are doing some kind of expensive
|
||||
setup or long-running work, or if you have a future that will keep doing some
|
||||
particular task indefinitely, you’ll need to think about when and where to hand
|
||||
control back to the runtime.</p>
|
||||
<p>Let’s simulate a long-running operation to illustrate the starvation problem,
|
||||
then explore how to solve it. Listing 17-14 introduces a <code>slow</code> function.</p>
|
||||
<figure class="listing" id="listing-17-14">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::{thread, time::Duration};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span><span class="boring"> // We will call `slow` here later
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>fn slow(name: &str, ms: u64) {
|
||||
thread::sleep(Duration::from_millis(ms));
|
||||
println!("'{name}' ran for {ms}ms");
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-17-14">Listing 17-14</a>: Using <code>thread::sleep</code> to simulate slow operations</figcaption>
|
||||
</figure>
|
||||
<p>This code uses <code>std::thread::sleep</code> instead of <code>trpl::sleep</code> so that calling
|
||||
<code>slow</code> will block the current thread for some number of milliseconds. We can
|
||||
use <code>slow</code> to stand in for real-world operations that are both long-running and
|
||||
blocking.</p>
|
||||
<p>In Listing 17-15, we use <code>slow</code> to emulate doing this kind of CPU-bound work in
|
||||
a pair of futures.</p>
|
||||
<figure class="listing" id="listing-17-15">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::{thread, time::Duration};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let a = async {
|
||||
println!("'a' started.");
|
||||
slow("a", 30);
|
||||
slow("a", 10);
|
||||
slow("a", 20);
|
||||
trpl::sleep(Duration::from_millis(50)).await;
|
||||
println!("'a' finished.");
|
||||
};
|
||||
|
||||
let b = async {
|
||||
println!("'b' started.");
|
||||
slow("b", 75);
|
||||
slow("b", 10);
|
||||
slow("b", 15);
|
||||
slow("b", 350);
|
||||
trpl::sleep(Duration::from_millis(50)).await;
|
||||
println!("'b' finished.");
|
||||
};
|
||||
|
||||
trpl::select(a, b).await;
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn slow(name: &str, ms: u64) {
|
||||
</span><span class="boring"> thread::sleep(Duration::from_millis(ms));
|
||||
</span><span class="boring"> println!("'{name}' ran for {ms}ms");
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-15">Listing 17-15</a>: Calling the <code>slow</code> function to simulate slow operations</figcaption>
|
||||
</figure>
|
||||
<p>Each future hands control back to the runtime only <em>after</em> carrying out a bunch
|
||||
of slow operations. If you run this code, you will see this output:</p>
|
||||
<!-- manual-regeneration
|
||||
cd listings/ch17-async-await/listing-17-15/
|
||||
cargo run
|
||||
copy just the output
|
||||
-->
|
||||
<pre><code class="language-text">'a' started.
|
||||
'a' ran for 30ms
|
||||
'a' ran for 10ms
|
||||
'a' ran for 20ms
|
||||
'b' started.
|
||||
'b' ran for 75ms
|
||||
'b' ran for 10ms
|
||||
'b' ran for 15ms
|
||||
'b' ran for 350ms
|
||||
'a' finished.
|
||||
</code></pre>
|
||||
<p>As with Listing 17-5 where we used <code>trpl::select</code> to race futures fetching two
|
||||
URLs, <code>select</code> still finishes as soon as <code>a</code> is done. There’s no interleaving
|
||||
between the calls to <code>slow</code> in the two futures, though. The <code>a</code> future does all
|
||||
of its work until the <code>trpl::sleep</code> call is awaited, then the <code>b</code> future does
|
||||
all of its work until its own <code>trpl::sleep</code> call is awaited, and finally the
|
||||
<code>a</code> future completes. To allow both futures to make progress between their slow
|
||||
tasks, we need await points so we can hand control back to the runtime. That
|
||||
means we need something we can await!</p>
|
||||
<p>We can already see this kind of handoff happening in Listing 17-15: if we
|
||||
removed the <code>trpl::sleep</code> at the end of the <code>a</code> future, it would complete
|
||||
without the <code>b</code> future running <em>at all</em>. Let’s try using the <code>trpl::sleep</code>
|
||||
function as a starting point for letting operations switch off making progress,
|
||||
as shown in Listing 17-16.</p>
|
||||
<figure class="listing" id="listing-17-16">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::{thread, time::Duration};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let one_ms = Duration::from_millis(1);
|
||||
|
||||
let a = async {
|
||||
println!("'a' started.");
|
||||
slow("a", 30);
|
||||
trpl::sleep(one_ms).await;
|
||||
slow("a", 10);
|
||||
trpl::sleep(one_ms).await;
|
||||
slow("a", 20);
|
||||
trpl::sleep(one_ms).await;
|
||||
println!("'a' finished.");
|
||||
};
|
||||
|
||||
let b = async {
|
||||
println!("'b' started.");
|
||||
slow("b", 75);
|
||||
trpl::sleep(one_ms).await;
|
||||
slow("b", 10);
|
||||
trpl::sleep(one_ms).await;
|
||||
slow("b", 15);
|
||||
trpl::sleep(one_ms).await;
|
||||
slow("b", 350);
|
||||
trpl::sleep(one_ms).await;
|
||||
println!("'b' finished.");
|
||||
};
|
||||
<span class="boring">
|
||||
</span><span class="boring"> trpl::select(a, b).await;
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn slow(name: &str, ms: u64) {
|
||||
</span><span class="boring"> thread::sleep(Duration::from_millis(ms));
|
||||
</span><span class="boring"> println!("'{name}' ran for {ms}ms");
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-16">Listing 17-16</a>: Using <code>trpl::sleep</code> to let operations switch off making progress</figcaption>
|
||||
</figure>
|
||||
<p>We’ve added <code>trpl::sleep</code> calls with await points between each call to <code>slow</code>.
|
||||
Now the two futures’ work is interleaved:</p>
|
||||
<!-- manual-regeneration
|
||||
cd listings/ch17-async-await/listing-17-16
|
||||
cargo run
|
||||
copy just the output
|
||||
-->
|
||||
<pre><code class="language-text">'a' started.
|
||||
'a' ran for 30ms
|
||||
'b' started.
|
||||
'b' ran for 75ms
|
||||
'a' ran for 10ms
|
||||
'b' ran for 10ms
|
||||
'a' ran for 20ms
|
||||
'b' ran for 15ms
|
||||
'a' finished.
|
||||
</code></pre>
|
||||
<p>The <code>a</code> future still runs for a bit before handing off control to <code>b</code>, because
|
||||
it calls <code>slow</code> before ever calling <code>trpl::sleep</code>, but after that the futures
|
||||
swap back and forth each time one of them hits an await point. In this case, we
|
||||
have done that after every call to <code>slow</code>, but we could break up the work in
|
||||
whatever way makes the most sense to us.</p>
|
||||
<p>We don’t really want to <em>sleep</em> here, though: we want to make progress as fast
|
||||
as we can. We just need to hand back control to the runtime. We can do that
|
||||
directly, using the <code>trpl::yield_now</code> function. In Listing 17-17, we replace
|
||||
all those <code>trpl::sleep</code> calls with <code>trpl::yield_now</code>.</p>
|
||||
<figure class="listing" id="listing-17-17">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::{thread, time::Duration};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let a = async {
|
||||
println!("'a' started.");
|
||||
slow("a", 30);
|
||||
trpl::yield_now().await;
|
||||
slow("a", 10);
|
||||
trpl::yield_now().await;
|
||||
slow("a", 20);
|
||||
trpl::yield_now().await;
|
||||
println!("'a' finished.");
|
||||
};
|
||||
|
||||
let b = async {
|
||||
println!("'b' started.");
|
||||
slow("b", 75);
|
||||
trpl::yield_now().await;
|
||||
slow("b", 10);
|
||||
trpl::yield_now().await;
|
||||
slow("b", 15);
|
||||
trpl::yield_now().await;
|
||||
slow("b", 350);
|
||||
trpl::yield_now().await;
|
||||
println!("'b' finished.");
|
||||
};
|
||||
<span class="boring">
|
||||
</span><span class="boring"> trpl::select(a, b).await;
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn slow(name: &str, ms: u64) {
|
||||
</span><span class="boring"> thread::sleep(Duration::from_millis(ms));
|
||||
</span><span class="boring"> println!("'{name}' ran for {ms}ms");
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-17">Listing 17-17</a>: Using <code>yield_now</code> to let operations switch off making progress</figcaption>
|
||||
</figure>
|
||||
<p>This code is both clearer about the actual intent and can be significantly
|
||||
faster than using <code>sleep</code>, because timers such as the one used by <code>sleep</code> often
|
||||
have limits on how granular they can be. The version of <code>sleep</code> we are using,
|
||||
for example, will always sleep for at least a millisecond, even if we pass it a
|
||||
<code>Duration</code> of one nanosecond. Again, modern computers are <em>fast</em>: they can do a
|
||||
lot in one millisecond!</p>
|
||||
<p>This means that async can be useful even for compute-bound tasks, depending on
|
||||
what else your program is doing, because it provides a useful tool for
|
||||
structuring the relationships between different parts of the program (but at a
|
||||
cost of the overhead of the async state machine). This is a form of
|
||||
<em>cooperative multitasking</em>, where each future has the power to determine when
|
||||
it hands over control via await points. Each future therefore also has the
|
||||
responsibility to avoid blocking for too long. In some Rust-based embedded
|
||||
operating systems, this is the <em>only</em> kind of multitasking!</p>
|
||||
<p>In real-world code, you won’t usually be alternating function calls with await
|
||||
points on every single line, of course. While yielding control in this way is
|
||||
relatively inexpensive, it’s not free. In many cases, trying to break up a
|
||||
compute-bound task might make it significantly slower, so sometimes it’s better
|
||||
for <em>overall</em> performance to let an operation block briefly. Always
|
||||
measure to see what your code’s actual performance bottlenecks are. The
|
||||
underlying dynamic is important to keep in mind, though, if you <em>are</em> seeing a
|
||||
lot of work happening in serial that you expected to happen concurrently!</p>
|
||||
<h3 id="building-our-own-async-abstractions"><a class="header" href="#building-our-own-async-abstractions">Building Our Own Async Abstractions</a></h3>
|
||||
<p>We can also compose futures together to create new patterns. For example, we can
|
||||
build a <code>timeout</code> function with async building blocks we already have. When
|
||||
we’re done, the result will be another building block we could use to create
|
||||
still more async abstractions.</p>
|
||||
<p>Listing 17-18 shows how we would expect this <code>timeout</code> to work with a slow
|
||||
future.</p>
|
||||
<figure class="listing" id="listing-17-18">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore does_not_compile"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let slow = async {
|
||||
trpl::sleep(Duration::from_secs(5)).await;
|
||||
"Finally finished"
|
||||
};
|
||||
|
||||
match timeout(slow, Duration::from_secs(2)).await {
|
||||
Ok(message) => println!("Succeeded with '{message}'"),
|
||||
Err(duration) => {
|
||||
println!("Failed after {} seconds", duration.as_secs())
|
||||
}
|
||||
}
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-18">Listing 17-18</a>: Using our imagined <code>timeout</code> to run a slow operation with a time limit</figcaption>
|
||||
</figure>
|
||||
<p>Let’s implement this! To begin, let’s think about the API for <code>timeout</code>:</p>
|
||||
<ul>
|
||||
<li>It needs to be an async function itself so we can await it.</li>
|
||||
<li>Its first parameter should be a future to run. We can make it generic to allow
|
||||
it to work with any future.</li>
|
||||
<li>Its second parameter will be the maximum time to wait. If we use a <code>Duration</code>,
|
||||
that will make it easy to pass along to <code>trpl::sleep</code>.</li>
|
||||
<li>It should return a <code>Result</code>. If the future completes successfully, the
|
||||
<code>Result</code> will be <code>Ok</code> with the value produced by the future. If the timeout
|
||||
elapses first, the <code>Result</code> will be <code>Err</code> with the duration that the timeout
|
||||
waited for.</li>
|
||||
</ul>
|
||||
<p>Listing 17-19 shows this declaration.</p>
|
||||
<!-- This is not tested because it intentionally does not compile. -->
|
||||
<figure class="listing" id="listing-17-19">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore does_not_compile"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span><span class="boring"> let slow = async {
|
||||
</span><span class="boring"> trpl::sleep(Duration::from_secs(5)).await;
|
||||
</span><span class="boring"> "Finally finished"
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> match timeout(slow, Duration::from_secs(2)).await {
|
||||
</span><span class="boring"> Ok(message) => println!("Succeeded with '{message}'"),
|
||||
</span><span class="boring"> Err(duration) => {
|
||||
</span><span class="boring"> println!("Failed after {} seconds", duration.as_secs())
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>async fn timeout<F: Future>(
|
||||
future_to_try: F,
|
||||
max_time: Duration,
|
||||
) -> Result<F::Output, Duration> {
|
||||
// Here is where our implementation will go!
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-17-19">Listing 17-19</a>: Defining the signature of <code>timeout</code></figcaption>
|
||||
</figure>
|
||||
<p>That satisfies our goals for the types. Now let’s think about the <em>behavior</em> we
|
||||
need: we want to race the future passed in against the duration. We can use
|
||||
<code>trpl::sleep</code> to make a timer future from the duration, and use <code>trpl::select</code>
|
||||
to run that timer with the future the caller passes in.</p>
|
||||
<p>In Listing 17-20, we implement <code>timeout</code> by matching on the result of awaiting
|
||||
<code>trpl::select</code>.</p>
|
||||
<figure class="listing" id="listing-17-20">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span>use trpl::Either;
|
||||
|
||||
// --snip--
|
||||
|
||||
<span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span><span class="boring"> let slow = async {
|
||||
</span><span class="boring"> trpl::sleep(Duration::from_secs(5)).await;
|
||||
</span><span class="boring"> "Finally finished"
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> match timeout(slow, Duration::from_secs(2)).await {
|
||||
</span><span class="boring"> Ok(message) => println!("Succeeded with '{message}'"),
|
||||
</span><span class="boring"> Err(duration) => {
|
||||
</span><span class="boring"> println!("Failed after {} seconds", duration.as_secs())
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>async fn timeout<F: Future>(
|
||||
future_to_try: F,
|
||||
max_time: Duration,
|
||||
) -> Result<F::Output, Duration> {
|
||||
match trpl::select(future_to_try, trpl::sleep(max_time)).await {
|
||||
Either::Left(output) => Ok(output),
|
||||
Either::Right(_) => Err(max_time),
|
||||
}
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-17-20">Listing 17-20</a>: Defining <code>timeout</code> with <code>select</code> and <code>sleep</code></figcaption>
|
||||
</figure>
|
||||
<p>The implementation of <code>trpl::select</code> is not fair: it always polls arguments in
|
||||
the order in which they are passed (other <code>select</code> implementations will
|
||||
randomly choose which argument to poll first). Thus, we pass <code>future_to_try</code> to
|
||||
<code>select</code> first so it gets a chance to complete even if <code>max_time</code> is a very
|
||||
short duration. If <code>future_to_try</code> finishes first, <code>select</code> will return <code>Left</code>
|
||||
with the output from <code>future_to_try</code>. If <code>timer</code> finishes first, <code>select</code> will
|
||||
return <code>Right</code> with the timer’s output of <code>()</code>.</p>
|
||||
<p>If the <code>future_to_try</code> succeeds and we get a <code>Left(output)</code>, we return
|
||||
<code>Ok(output)</code>. If the sleep timer elapses instead and we get a <code>Right(())</code>, we
|
||||
ignore the <code>()</code> with <code>_</code> and return <code>Err(max_time)</code> instead.</p>
|
||||
<p>With that, we have a working <code>timeout</code> built out of two other async helpers. If
|
||||
we run our code, it will print the failure mode after the timeout:</p>
|
||||
<pre><code class="language-text">Failed after 2 seconds
|
||||
</code></pre>
|
||||
<p>Because futures compose with other futures, you can build really powerful tools
|
||||
using smaller async building blocks. For example, you can use this same
|
||||
approach to combine timeouts with retries, and in turn use those with
|
||||
operations such as network calls (such as those in Listing 17-5).</p>
|
||||
<p>In practice, you’ll usually work directly with <code>async</code> and <code>await</code>, and
|
||||
secondarily with functions such as <code>select</code> and macros such as the <code>join!</code>
|
||||
macro to control how the outermost futures are executed.</p>
|
||||
<p>We’ve now seen a number of ways to work with multiple futures at the same time.
|
||||
Up next, we’ll look at how we can work with multiple futures in a sequence over
|
||||
time with <em>streams</em>.</p>
|
||||
</body>
|
||||
</html>
|
||||
12
ch17/ch17-03-oo-design-patterns.html
Normal file
12
ch17/ch17-03-oo-design-patterns.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Redirecting...</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Redirecting to... <a href="../ch18/ch18-03-oo-design-patterns.html">ch18-03-oo-design-patterns.html</a>.</p>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
125
ch17/ch17-04-streams.html
Normal file
125
ch17/ch17-04-streams.html
Normal file
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Streams: Futures in Sequence</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="streams"></a></p>
|
||||
<h2 id="streams-futures-in-sequence"><a class="header" href="#streams-futures-in-sequence">Streams: Futures in Sequence</a></h2>
|
||||
<p>Recall how we used the receiver for our async channel earlier in this chapter
|
||||
in the <a href="ch17-02-concurrency-with-async.html#message-passing">“Message Passing”</a><!-- ignore --> section. The async
|
||||
<code>recv</code> method produces a sequence of items over time. This is an instance of a
|
||||
much more general pattern known as a <em>stream</em>. Many concepts are naturally
|
||||
represented as streams: items becoming available in a queue, chunks of data
|
||||
being pulled incrementally from the filesystem when the full data set is too
|
||||
large for the computer’s memory, or data arriving over the network over time.
|
||||
Because streams are futures, we can use them with any other kind of future and
|
||||
combine them in interesting ways. For example, we can batch up events to avoid
|
||||
triggering too many network calls, set timeouts on sequences of long-running
|
||||
operations, or throttle user interface events to avoid doing needless work.</p>
|
||||
<p>We saw a sequence of items back in Chapter 13, when we looked at the Iterator
|
||||
trait in <a href="../ch13/ch13-02-iterators.html#the-iterator-trait-and-the-next-method">“The Iterator Trait and the <code>next</code> Method”</a><!--
|
||||
ignore --> section, but there are two differences between iterators and the
|
||||
async channel receiver. The first difference is time: iterators are
|
||||
synchronous, while the channel receiver is asynchronous. The second difference
|
||||
is the API. When working directly with <code>Iterator</code>, we call its synchronous
|
||||
<code>next</code> method. With the <code>trpl::Receiver</code> stream in particular, we called an
|
||||
asynchronous <code>recv</code> method instead. Otherwise, these APIs feel very similar,
|
||||
and that similarity isn’t a coincidence. A stream is like an asynchronous form
|
||||
of iteration. Whereas the <code>trpl::Receiver</code> specifically waits to receive
|
||||
messages, though, the general-purpose stream API is much broader: it provides
|
||||
the next item the way <code>Iterator</code> does, but asynchronously.</p>
|
||||
<p>The similarity between iterators and streams in Rust means we can actually
|
||||
create a stream from any iterator. As with an iterator, we can work with a
|
||||
stream by calling its <code>next</code> method and then awaiting the output, as in Listing
|
||||
17-21, which won’t compile yet.</p>
|
||||
<figure class="listing" id="listing-17-21">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore does_not_compile"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span> let values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let iter = values.iter().map(|n| n * 2);
|
||||
let mut stream = trpl::stream_from_iter(iter);
|
||||
|
||||
while let Some(value) = stream.next().await {
|
||||
println!("The value was: {value}");
|
||||
}
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-21">Listing 17-21</a>: Creating a stream from an iterator and printing its values</figcaption>
|
||||
</figure>
|
||||
<p>We start with an array of numbers, which we convert to an iterator and then
|
||||
call <code>map</code> on to double all the values. Then we convert the iterator into a
|
||||
stream using the <code>trpl::stream_from_iter</code> function. Next, we loop over the
|
||||
items in the stream as they arrive with the <code>while let</code> loop.</p>
|
||||
<p>Unfortunately, when we try to run the code, it doesn’t compile but instead
|
||||
reports that there’s no <code>next</code> method available:</p>
|
||||
<!-- manual-regeneration
|
||||
cd listings/ch17-async-await/listing-17-21
|
||||
cargo build
|
||||
copy only the error output
|
||||
-->
|
||||
<pre><code class="language-text">error[E0599]: no method named `next` found for struct `tokio_stream::iter::Iter` in the current scope
|
||||
--> src/main.rs:10:40
|
||||
|
|
||||
10 | while let Some(value) = stream.next().await {
|
||||
| ^^^^
|
||||
|
|
||||
= help: items from traits can only be used if the trait is in scope
|
||||
help: the following traits which provide `next` are implemented but not in scope; perhaps you want to import one of them
|
||||
|
|
||||
1 + use crate::trpl::StreamExt;
|
||||
|
|
||||
1 + use futures_util::stream::stream::StreamExt;
|
||||
|
|
||||
1 + use std::iter::Iterator;
|
||||
|
|
||||
1 + use std::str::pattern::Searcher;
|
||||
|
|
||||
help: there is a method `try_next` with a similar name
|
||||
|
|
||||
10 | while let Some(value) = stream.try_next().await {
|
||||
| ~~~~~~~~
|
||||
</code></pre>
|
||||
<p>As this output explains, the reason for the compiler error is that we need the
|
||||
right trait in scope to be able to use the <code>next</code> method. Given our discussion
|
||||
so far, you might reasonably expect that trait to be <code>Stream</code>, but it’s
|
||||
actually <code>StreamExt</code>. Short for <em>extension</em>, <code>Ext</code> is a common pattern in the
|
||||
Rust community for extending one trait with another.</p>
|
||||
<p>The <code>Stream</code> trait defines a low-level interface that effectively combines the
|
||||
<code>Iterator</code> and <code>Future</code> traits. <code>StreamExt</code> supplies a higher-level set of APIs
|
||||
on top of <code>Stream</code>, including the <code>next</code> method as well as other utility
|
||||
methods similar to those provided by the <code>Iterator</code> trait. <code>Stream</code> and
|
||||
<code>StreamExt</code> are not yet part of Rust’s standard library, but most ecosystem
|
||||
crates use similar definitions.</p>
|
||||
<p>The fix to the compiler error is to add a <code>use</code> statement for
|
||||
<code>trpl::StreamExt</code>, as in Listing 17-22.</p>
|
||||
<figure class="listing" id="listing-17-22">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span>use trpl::StreamExt;
|
||||
|
||||
fn main() {
|
||||
trpl::block_on(async {
|
||||
let values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
// --snip--
|
||||
<span class="boring"> let iter = values.iter().map(|n| n * 2);
|
||||
</span><span class="boring"> let mut stream = trpl::stream_from_iter(iter);
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> while let Some(value) = stream.next().await {
|
||||
</span><span class="boring"> println!("The value was: {value}");
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-22">Listing 17-22</a>: Successfully using an iterator as the basis for a stream</figcaption>
|
||||
</figure>
|
||||
<p>With all those pieces put together, this code works the way we want! What’s
|
||||
more, now that we have <code>StreamExt</code> in scope, we can use all of its utility
|
||||
methods, just as with iterators.</p>
|
||||
</body>
|
||||
</html>
|
||||
563
ch17/ch17-05-traits-for-async.html
Normal file
563
ch17/ch17-05-traits-for-async.html
Normal file
@@ -0,0 +1,563 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>A Closer Look at the Traits for Async</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="digging-into-the-traits-for-async"></a></p>
|
||||
<h2 id="a-closer-look-at-the-traits-for-async"><a class="header" href="#a-closer-look-at-the-traits-for-async">A Closer Look at the Traits for Async</a></h2>
|
||||
<p>Throughout the chapter, we’ve used the <code>Future</code>, <code>Stream</code>, and <code>StreamExt</code>
|
||||
traits in various ways. So far, though, we’ve avoided getting too far into the
|
||||
details of how they work or how they fit together, which is fine most of the
|
||||
time for your day-to-day Rust work. Sometimes, though, you’ll encounter
|
||||
situations where you’ll need to understand a few more of these traits’ details,
|
||||
along with the <code>Pin</code> type and the <code>Unpin</code> trait. In this section, we’ll dig in
|
||||
just enough to help in those scenarios, still leaving the <em>really</em> deep dive
|
||||
for other documentation.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="future"></a></p>
|
||||
<h3 id="the-future-trait"><a class="header" href="#the-future-trait">The <code>Future</code> Trait</a></h3>
|
||||
<p>Let’s start by taking a closer look at how the <code>Future</code> trait works. Here’s how
|
||||
Rust defines it:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>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>;
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>That trait definition includes a bunch of new types and also some syntax we
|
||||
haven’t seen before, so let’s walk through the definition piece by piece.</p>
|
||||
<p>First, <code>Future</code>’s associated type <code>Output</code> says what the future resolves to.
|
||||
This is analogous to the <code>Item</code> associated type for the <code>Iterator</code> trait.
|
||||
Second, <code>Future</code> has the <code>poll</code> method, which takes a special <code>Pin</code> reference
|
||||
for its <code>self</code> parameter and a mutable reference to a <code>Context</code> type, and
|
||||
returns a <code>Poll<Self::Output></code>. We’ll talk more about <code>Pin</code> and <code>Context</code> in a
|
||||
moment. For now, let’s focus on what the method returns, the <code>Poll</code> type:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>pub enum Poll<T> {
|
||||
Ready(T),
|
||||
Pending,
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>This <code>Poll</code> type is similar to an <code>Option</code>. It has one variant that has a value,
|
||||
<code>Ready(T)</code>, and one that does not, <code>Pending</code>. <code>Poll</code> means something quite
|
||||
different from <code>Option</code>, though! The <code>Pending</code> variant indicates that the future
|
||||
still has work to do, so the caller will need to check again later. The <code>Ready</code>
|
||||
variant indicates that the <code>Future</code> has finished its work and the <code>T</code> value is
|
||||
available.</p>
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: It’s rare to need to call <code>poll</code> directly, but if you do need to, keep
|
||||
in mind that with most futures, the caller should not call <code>poll</code> again after
|
||||
the future has returned <code>Ready</code>. Many futures will panic if polled again after
|
||||
becoming ready. Futures that are safe to poll again will say so explicitly in
|
||||
their documentation. This is similar to how <code>Iterator::next</code> behaves.</p>
|
||||
</section>
|
||||
<p>When you see code that uses <code>await</code>, Rust compiles it under the hood to code
|
||||
that calls <code>poll</code>. If you look back at Listing 17-4, where we printed out the
|
||||
page title for a single URL once it resolved, Rust compiles it into something
|
||||
kind of (although not exactly) like this:</p>
|
||||
<pre><code class="language-rust ignore">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?
|
||||
}
|
||||
}</code></pre>
|
||||
<p>What should we do when the future is still <code>Pending</code>? We need some way to try
|
||||
again, and again, and again, until the future is finally ready. In other words,
|
||||
we need a loop:</p>
|
||||
<pre><code class="language-rust ignore">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
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
<p>If Rust compiled it to exactly that code, though, every <code>await</code> would be
|
||||
blocking—exactly the opposite of what we were going for! Instead, Rust ensures
|
||||
that the loop can hand off control to something that can pause work on this
|
||||
future to work on other futures and then check this one again later. As we’ve
|
||||
seen, that something is an async runtime, and this scheduling and coordination
|
||||
work is one of its main jobs.</p>
|
||||
<p>In the <a href="ch17-02-concurrency-with-async.html#sending-data-between-two-tasks-using-message-passing">“Sending Data Between Two Tasks Using Message
|
||||
Passing”</a><!-- ignore --> section, we described waiting on
|
||||
<code>rx.recv</code>. The <code>recv</code> call returns a future, and awaiting the future polls it.
|
||||
We noted that a runtime will pause the future until it’s ready with either
|
||||
<code>Some(message)</code> or <code>None</code> when the channel closes. With our deeper
|
||||
understanding of the <code>Future</code> trait, and specifically <code>Future::poll</code>, we can
|
||||
see how that works. The runtime knows the future isn’t ready when it returns
|
||||
<code>Poll::Pending</code>. Conversely, the runtime knows the future <em>is</em> ready and
|
||||
advances it when <code>poll</code> returns <code>Poll::Ready(Some(message))</code> or
|
||||
<code>Poll::Ready(None)</code>.</p>
|
||||
<p>The exact details of how a runtime does that are beyond the scope of this book,
|
||||
but the key is to see the basic mechanics of futures: a runtime <em>polls</em> each
|
||||
future it is responsible for, putting the future back to sleep when it is not
|
||||
yet ready.</p>
|
||||
<!-- Old headings. Do not remove or links may break. -->
|
||||
<p><a id="pinning-and-the-pin-and-unpin-traits"></a>
|
||||
<a id="the-pin-and-unpin-traits"></a></p>
|
||||
<h3 id="the-pin-type-and-the-unpin-trait"><a class="header" href="#the-pin-type-and-the-unpin-trait">The <code>Pin</code> Type and the <code>Unpin</code> Trait</a></h3>
|
||||
<p>Back in Listing 17-13, we used the <code>trpl::join!</code> macro to await three
|
||||
futures. However, it’s common to have a collection such as a vector containing
|
||||
some number futures that won’t be known until runtime. Let’s change Listing
|
||||
17-13 to the code in Listing 17-23 that puts the three futures into a vector
|
||||
and calls the <code>trpl::join_all</code> function instead, which won’t compile yet.</p>
|
||||
<figure class="listing" id="listing-17-23">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre><code class="language-rust ignore does_not_compile"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span><span class="boring"> let (tx, mut rx) = trpl::channel();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let tx1 = tx.clone();
|
||||
</span><span class="boring"> let tx1_fut = async move {
|
||||
</span><span class="boring"> let vals = vec![
|
||||
</span><span class="boring"> String::from("hi"),
|
||||
</span><span class="boring"> String::from("from"),
|
||||
</span><span class="boring"> String::from("the"),
|
||||
</span><span class="boring"> String::from("future"),
|
||||
</span><span class="boring"> ];
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for val in vals {
|
||||
</span><span class="boring"> tx1.send(val).unwrap();
|
||||
</span><span class="boring"> trpl::sleep(Duration::from_secs(1)).await;
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let rx_fut = async {
|
||||
</span><span class="boring"> while let Some(value) = rx.recv().await {
|
||||
</span><span class="boring"> println!("received '{value}'");
|
||||
</span><span class="boring"> }
|
||||
</span><span class="boring"> };
|
||||
</span><span class="boring">
|
||||
</span> let tx_fut = async move {
|
||||
// --snip--
|
||||
<span class="boring"> let vals = vec![
|
||||
</span><span class="boring"> String::from("more"),
|
||||
</span><span class="boring"> String::from("messages"),
|
||||
</span><span class="boring"> String::from("for"),
|
||||
</span><span class="boring"> String::from("you"),
|
||||
</span><span class="boring"> ];
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for val in vals {
|
||||
</span><span class="boring"> tx.send(val).unwrap();
|
||||
</span><span class="boring"> trpl::sleep(Duration::from_secs(1)).await;
|
||||
</span><span class="boring"> }
|
||||
</span> };
|
||||
|
||||
let futures: Vec<Box<dyn Future<Output = ()>>> =
|
||||
vec![Box::new(tx1_fut), Box::new(rx_fut), Box::new(tx_fut)];
|
||||
|
||||
trpl::join_all(futures).await;
|
||||
<span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-23">Listing 17-23</a>: Awaiting futures in a collection</figcaption>
|
||||
</figure>
|
||||
<p>We put each future within a <code>Box</code> to make them into <em>trait objects</em>, just as
|
||||
we did in the “Returning Errors from <code>run</code>” section 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 <code>Future</code> trait.</p>
|
||||
<p>This might be surprising. After all, none of the async blocks returns anything,
|
||||
so each one produces a <code>Future<Output = ()></code>. Remember that <code>Future</code> is a
|
||||
trait, though, and that the compiler creates a unique enum for each async
|
||||
block, even when they have identical output types. Just as you can’t put two
|
||||
different handwritten structs in a <code>Vec</code>, you can’t mix compiler-generated
|
||||
enums.</p>
|
||||
<p>Then we pass the collection of futures to the <code>trpl::join_all</code> function and
|
||||
await the result. However, this doesn’t compile; here’s the relevant part of
|
||||
the error messages.</p>
|
||||
<!-- manual-regeneration
|
||||
cd listings/ch17-async-await/listing-17-23
|
||||
cargo build
|
||||
copy *only* the final `error` block from the errors
|
||||
-->
|
||||
<pre><code class="language-text">error[E0277]: `dyn Future<Output = ()>` cannot be unpinned
|
||||
--> src/main.rs:48:33
|
||||
|
|
||||
48 | trpl::join_all(futures).await;
|
||||
| ^^^^^ the trait `Unpin` is not implemented for `dyn Future<Output = ()>`
|
||||
|
|
||||
= 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<dyn Future<Output = ()>>` to implement `Future`
|
||||
note: required by a bound in `futures_util::future::join_all::JoinAll`
|
||||
--> file:///home/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/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`
|
||||
</code></pre>
|
||||
<p>The note in this error message tells us that we should use the <code>pin!</code> macro to
|
||||
<em>pin</em> the values, which means putting them inside the <code>Pin</code> type that
|
||||
guarantees the values won’t be moved in memory. The error message says pinning
|
||||
is required because <code>dyn Future<Output = ()></code> needs to implement the <code>Unpin</code>
|
||||
trait and it currently does not.</p>
|
||||
<p>The <code>trpl::join_all</code> function returns a struct called <code>JoinAll</code>. That struct is
|
||||
generic over a type <code>F</code>, which is constrained to implement the <code>Future</code> trait.
|
||||
Directly awaiting a future with <code>await</code> pins the future implicitly. That’s why
|
||||
we don’t need to use <code>pin!</code> everywhere we want to await futures.</p>
|
||||
<p>However, we’re not directly awaiting a future here. Instead, we construct a new
|
||||
future, JoinAll, by passing a collection of futures to the <code>join_all</code> function.
|
||||
The signature for <code>join_all</code> requires that the types of the items in the
|
||||
collection all implement the <code>Future</code> trait, and <code>Box<T></code> implements <code>Future</code>
|
||||
only if the <code>T</code> it wraps is a future that implements the <code>Unpin</code> trait.</p>
|
||||
<p>That’s a lot to absorb! To really understand it, let’s dive a little further
|
||||
into how the <code>Future</code> trait actually works, in particular around pinning. Look
|
||||
again at the definition of the <code>Future</code> trait:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>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>;
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>The <code>cx</code> parameter and its <code>Context</code> type are the key to how a runtime actually
|
||||
knows when to check any given future while still being lazy. Again, 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 <code>Future</code> implementation. We’ll
|
||||
focus instead on the type for <code>self</code>, as this is the first time we’ve seen a
|
||||
method where <code>self</code> has a type annotation. A type annotation for <code>self</code> works
|
||||
like type annotations for other function parameters but with two key
|
||||
differences:</p>
|
||||
<ul>
|
||||
<li>It tells Rust what type <code>self</code> must be for the method to be called.</li>
|
||||
<li>It can’t be just any type. It’s restricted to the type on which the method is
|
||||
implemented, a reference or smart pointer to that type, or a <code>Pin</code> wrapping a
|
||||
reference to that type.</li>
|
||||
</ul>
|
||||
<p>We’ll see more on this syntax in <a href="../ch18/ch18-00-oop.html">Chapter 18</a><!-- ignore -->. For now,
|
||||
it’s enough to know that if we want to poll a future to check whether it is
|
||||
<code>Pending</code> or <code>Ready(Output)</code>, we need a <code>Pin</code>-wrapped mutable reference to the
|
||||
type.</p>
|
||||
<p><code>Pin</code> is a wrapper for pointer-like types such as <code>&</code>, <code>&mut</code>, <code>Box</code>, and <code>Rc</code>.
|
||||
(Technically, <code>Pin</code> works with types that implement the <code>Deref</code> or <code>DerefMut</code>
|
||||
traits, but this is effectively equivalent to working only with references and
|
||||
smart pointers.) <code>Pin</code> is not a pointer itself and doesn’t have any behavior of
|
||||
its own like <code>Rc</code> and <code>Arc</code> do with reference counting; it’s purely a tool the
|
||||
compiler can use to enforce constraints on pointer usage.</p>
|
||||
<p>Recalling that <code>await</code> is implemented in terms of calls to <code>poll</code> starts to
|
||||
explain the error message we saw earlier, but that was in terms of <code>Unpin</code>, not
|
||||
<code>Pin</code>. So how exactly does <code>Pin</code> relate to <code>Unpin</code>, and why does <code>Future</code> need
|
||||
<code>self</code> to be in a <code>Pin</code> type to call <code>poll</code>?</p>
|
||||
<p>Remember from earlier in this chapter that a series of await points in a future
|
||||
get compiled into a state machine, and the compiler makes sure that state
|
||||
machine follows all of Rust’s normal rules around safety, including borrowing
|
||||
and ownership. To make that work, Rust looks at what data is needed between one
|
||||
await point and either the next await point or the end of the async block. It
|
||||
then creates a corresponding variant in the compiled state machine. Each
|
||||
variant gets the access it needs to the data that will be used in that section
|
||||
of the source code, whether by taking ownership of that data or by getting a
|
||||
mutable or immutable reference to it.</p>
|
||||
<p>So far, so good: if we get anything wrong about the ownership or references in
|
||||
a given async block, the borrow checker will tell us. When we want to move
|
||||
around the future that corresponds to that block—like moving it into a <code>Vec</code> to
|
||||
pass to <code>join_all</code>—things get trickier.</p>
|
||||
<p>When we move a future—whether by pushing it into a data structure to use as an
|
||||
iterator with <code>join_all</code> or by returning it from a function—that actually means
|
||||
moving the state machine Rust creates for us. And unlike most other types in
|
||||
Rust, the futures Rust creates for async blocks can end up with references to
|
||||
themselves in the fields of any given variant, as shown in the simplified illustration in Figure 17-4.</p>
|
||||
<figure>
|
||||
<img alt="A single-column, three-row table representing a future, fut1, which has data values 0 and 1 in the first two rows and an arrow pointing from the third row back to the second row, representing an internal reference within the future." src="../img/trpl17-04.svg" class="center" />
|
||||
<figcaption>Figure 17-4: A self-referential data type</figcaption>
|
||||
</figure>
|
||||
<p>By default, though, any object that has a reference to itself is unsafe to move,
|
||||
because references always point to the actual memory address of whatever they
|
||||
refer to (see Figure 17-5). If you move the data structure itself, those
|
||||
internal references will be left pointing to the old location. However, that
|
||||
memory location is now invalid. For one thing, its value will not be updated
|
||||
when you make changes to the data structure. For another—more important—thing,
|
||||
the computer is now free to reuse that memory for other purposes! You could end
|
||||
up reading completely unrelated data later.</p>
|
||||
<figure>
|
||||
<img alt="Two tables, depicting two futures, fut1 and fut2, each of which has one column and three rows, representing the result of having moved a future out of fut1 into fut2. The first, fut1, is grayed out, with a question mark in each index, representing unknown memory. The second, fut2, has 0 and 1 in the first and second rows and an arrow pointing from its third row back to the second row of fut1, representing a pointer that is referencing the old location in memory of the future before it was moved." src="../img/trpl17-05.svg" class="center" />
|
||||
<figcaption>Figure 17-5: The unsafe result of moving a self-referential data type</figcaption>
|
||||
</figure>
|
||||
<p>Theoretically, the Rust compiler could try to update every reference to an
|
||||
object whenever it gets moved, but that could add a lot of performance overhead,
|
||||
especially if a whole web of references needs updating. If we could instead make
|
||||
sure the data structure in question <em>doesn’t move in memory</em>, we wouldn’t have
|
||||
to update any references. This is exactly what Rust’s borrow checker is for:
|
||||
in safe code, it prevents you from moving any item with an active reference to
|
||||
it.</p>
|
||||
<p><code>Pin</code> builds on that to give us the exact guarantee we need. When we <em>pin</em> a
|
||||
value by wrapping a pointer to that value in <code>Pin</code>, it can no longer move. Thus,
|
||||
if you have <code>Pin<Box<SomeType>></code>, you actually pin the <code>SomeType</code> value, <em>not</em>
|
||||
the <code>Box</code> pointer. Figure 17-6 illustrates this process.</p>
|
||||
<figure>
|
||||
<img alt="Three boxes laid out side by side. The first is labeled “Pin”, the second “b1”, and the third “pinned”. Within “pinned” is a table labeled “fut”, with a single column; it represents a future with cells for each part of the data structure. Its first cell has the value “0”, its second cell has an arrow coming out of it and pointing to the fourth and final cell, which has the value “1” in it, and the third cell has dashed lines and an ellipsis to indicate there may be other parts to the data structure. All together, the “fut” table represents a future which is self-referential. An arrow leaves the box labeled “Pin”, goes through the box labeled “b1” and terminates inside the “pinned” box at the “fut” table." src="../img/trpl17-06.svg" class="center" />
|
||||
<figcaption>Figure 17-6: Pinning a `Box` that points to a self-referential future type</figcaption>
|
||||
</figure>
|
||||
<p>In fact, the <code>Box</code> pointer can still move around freely. Remember: we care about
|
||||
making sure the data ultimately being referenced stays in place. If a pointer
|
||||
moves around, <em>but the data it points to</em> is in the same place, as in Figure
|
||||
17-7, there’s no potential problem. (As an independent exercise, look at the docs
|
||||
for the types as well as the <code>std::pin</code> module and try to work out how you’d do
|
||||
this with a <code>Pin</code> wrapping a <code>Box</code>.) The key is that the self-referential type
|
||||
itself cannot move, because it is still pinned.</p>
|
||||
<figure>
|
||||
<img alt="Four boxes laid out in three rough columns, identical to the previous diagram with a change to the second column. Now there are two boxes in the second column, labeled “b1” and “b2”, “b1” is grayed out, and the arrow from “Pin” goes through “b2” instead of “b1”, indicating that the pointer has moved from “b1” to “b2”, but the data in “pinned” has not moved." src="../img/trpl17-07.svg" class="center" />
|
||||
<figcaption>Figure 17-7: Moving a `Box` which points to a self-referential future type</figcaption>
|
||||
</figure>
|
||||
<p>However, most types are perfectly safe to move around, even if they happen to be
|
||||
behind a <code>Pin</code> pointer. We only need to think about pinning when items have
|
||||
internal references. Primitive values such as numbers and Booleans are safe
|
||||
because they obviously don’t have any internal references.
|
||||
Neither do most types you normally work with in Rust. You can move around
|
||||
a <code>Vec</code>, for example, without worrying. Given what we have seen so far, if
|
||||
you have a <code>Pin<Vec<String>></code>, you’d have to do everything via the safe but
|
||||
restrictive APIs provided by <code>Pin</code>, even though a <code>Vec<String></code> is always safe
|
||||
to move if there are no other references to it. We need a way to tell the
|
||||
compiler that it’s fine to move items around in cases like this—and that’s
|
||||
where <code>Unpin</code> comes into play.</p>
|
||||
<p><code>Unpin</code> is a marker trait, similar to the <code>Send</code> and <code>Sync</code> traits we saw in
|
||||
Chapter 16, and thus has no functionality of its own. Marker traits exist only
|
||||
to tell the compiler it’s safe to use the type implementing a given trait in a
|
||||
particular context. <code>Unpin</code> informs the compiler that a given type does <em>not</em>
|
||||
need to uphold any guarantees about whether the value in question can be safely
|
||||
moved.</p>
|
||||
<!--
|
||||
The inline `<code>` in the next block is to allow the inline `<em>` inside it,
|
||||
matching what NoStarch does style-wise, and emphasizing within the text here
|
||||
that it is something distinct from a normal type.
|
||||
-->
|
||||
<p>Just as with <code>Send</code> and <code>Sync</code>, the compiler implements <code>Unpin</code> automatically
|
||||
for all types where it can prove it is safe. A special case, again similar to
|
||||
<code>Send</code> and <code>Sync</code>, is where <code>Unpin</code> is <em>not</em> implemented for a type. The
|
||||
notation for this is <code>impl !Unpin for <em>SomeType</em></code>, where
|
||||
<code><em>SomeType</em></code> is the name of a type that <em>does</em> need to uphold
|
||||
those guarantees to be safe whenever a pointer to that type is used in a <code>Pin</code>.</p>
|
||||
<p>In other words, there are two things to keep in mind about the relationship
|
||||
between <code>Pin</code> and <code>Unpin</code>. First, <code>Unpin</code> is the “normal” case, and <code>!Unpin</code> is
|
||||
the special case. Second, whether a type implements <code>Unpin</code> or <code>!Unpin</code> <em>only</em>
|
||||
matters when you’re using a pinned pointer to that type like <code>Pin<&mut
|
||||
<em>SomeType</em>></code>.</p>
|
||||
<p>To make that concrete, think about a <code>String</code>: it has a length and the Unicode
|
||||
characters that make it up. We can wrap a <code>String</code> in <code>Pin</code>, as seen in Figure
|
||||
17-8. However, <code>String</code> automatically implements <code>Unpin</code>, as do most other types
|
||||
in Rust.</p>
|
||||
<figure>
|
||||
<img alt="A box labeled “Pin” on the left with an arrow going from it to a box labeled “String” on the right. The “String” box contains the data 5usize, representing the length of the string, and the letters “h”, “e”, “l”, “l”, and “o” representing the characters of the string “hello” stored in this String instance. A dotted rectangle surrounds the “String” box and its label, but not the “Pin” box." src="../img/trpl17-08.svg" class="center" />
|
||||
<figcaption>Figure 17-8: Pinning a `String`; the dotted line indicates that the `String` implements the `Unpin` trait and thus is not pinned</figcaption>
|
||||
</figure>
|
||||
<p>As a result, we can do things that would be illegal if <code>String</code> implemented
|
||||
<code>!Unpin</code> instead, such as replacing one string with another at the exact same
|
||||
location in memory as in Figure 17-9. This doesn’t violate the <code>Pin</code> contract,
|
||||
because <code>String</code> has no internal references that make it unsafe to move around.
|
||||
That is precisely why it implements <code>Unpin</code> rather than <code>!Unpin</code>.</p>
|
||||
<figure>
|
||||
<img alt="The same “hello” string data from the previous example, now labeled “s1” and grayed out. The “Pin” box from the previous example now points to a different String instance, one that is labeled “s2”, is valid, has a length of 7usize, and contains the characters of the string “goodbye”. s2 is surrounded by a dotted rectangle because it, too, implements the Unpin trait." src="../img/trpl17-09.svg" class="center" />
|
||||
<figcaption>Figure 17-9: Replacing the `String` with an entirely different `String` in memory</figcaption>
|
||||
</figure>
|
||||
<p>Now we know enough to understand the errors reported for that <code>join_all</code> call
|
||||
from back in Listing 17-23. We originally tried to move the futures produced by
|
||||
async blocks into a <code>Vec<Box<dyn Future<Output = ()>>></code>, but as we’ve seen,
|
||||
those futures may have internal references, so they don’t automatically
|
||||
implement <code>Unpin</code>. Once we pin them, we can pass the resulting <code>Pin</code> type into
|
||||
the <code>Vec</code>, confident that the underlying data in the futures will <em>not</em> be
|
||||
moved. Listing 17-24 shows how to fix the code by calling the <code>pin!</code> macro
|
||||
where each of the three futures are defined and adjusting the trait object type.</p>
|
||||
<figure class="listing" id="listing-17-24">
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // required for mdbook test
|
||||
</span><span class="boring">
|
||||
</span>use std::pin::{Pin, pin};
|
||||
|
||||
// --snip--
|
||||
|
||||
<span class="boring">use std::time::Duration;
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring"> trpl::block_on(async {
|
||||
</span><span class="boring"> let (tx, mut rx) = trpl::channel();
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> let tx1 = tx.clone();
|
||||
</span> let tx1_fut = pin!(async move {
|
||||
// --snip--
|
||||
<span class="boring"> let vals = vec![
|
||||
</span><span class="boring"> String::from("hi"),
|
||||
</span><span class="boring"> String::from("from"),
|
||||
</span><span class="boring"> String::from("the"),
|
||||
</span><span class="boring"> String::from("future"),
|
||||
</span><span class="boring"> ];
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for val in vals {
|
||||
</span><span class="boring"> tx1.send(val).unwrap();
|
||||
</span><span class="boring"> trpl::sleep(Duration::from_secs(1)).await;
|
||||
</span><span class="boring"> }
|
||||
</span> });
|
||||
|
||||
let rx_fut = pin!(async {
|
||||
// --snip--
|
||||
<span class="boring"> while let Some(value) = rx.recv().await {
|
||||
</span><span class="boring"> println!("received '{value}'");
|
||||
</span><span class="boring"> }
|
||||
</span> });
|
||||
|
||||
let tx_fut = pin!(async move {
|
||||
// --snip--
|
||||
<span class="boring"> let vals = vec![
|
||||
</span><span class="boring"> String::from("more"),
|
||||
</span><span class="boring"> String::from("messages"),
|
||||
</span><span class="boring"> String::from("for"),
|
||||
</span><span class="boring"> String::from("you"),
|
||||
</span><span class="boring"> ];
|
||||
</span><span class="boring">
|
||||
</span><span class="boring"> for val in vals {
|
||||
</span><span class="boring"> tx.send(val).unwrap();
|
||||
</span><span class="boring"> trpl::sleep(Duration::from_secs(1)).await;
|
||||
</span><span class="boring"> }
|
||||
</span> });
|
||||
|
||||
let futures: Vec<Pin<&mut dyn Future<Output = ()>>> =
|
||||
vec![tx1_fut, rx_fut, tx_fut];
|
||||
<span class="boring">
|
||||
</span><span class="boring"> trpl::join_all(futures).await;
|
||||
</span><span class="boring"> });
|
||||
</span><span class="boring">}</span></code></pre>
|
||||
<figcaption><a href="#listing-17-24">Listing 17-24</a>: Pinning the futures to enable moving them into the vector</figcaption>
|
||||
</figure>
|
||||
<p>This example now compiles and runs, and we could add or remove futures from the
|
||||
vector at runtime and join them all.</p>
|
||||
<p><code>Pin</code> and <code>Unpin</code> are mostly important for building lower-level libraries, or
|
||||
when you’re building a runtime itself, rather than for day-to-day Rust code.
|
||||
When you see these traits in error messages, though, now you’ll have a better
|
||||
idea of how to fix your code!</p>
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: This combination of <code>Pin</code> and <code>Unpin</code> makes it possible to safely
|
||||
implement a whole class of complex types in Rust that would otherwise prove
|
||||
challenging because they’re self-referential. Types that require <code>Pin</code> show up
|
||||
most commonly in async Rust today, but every once in a while, you might see
|
||||
them in other contexts, too.</p>
|
||||
<p>The specifics of how <code>Pin</code> and <code>Unpin</code> work, and the rules they’re required
|
||||
to uphold, are covered extensively in the API documentation for <code>std::pin</code>, so
|
||||
if you’re interested in learning more, that’s a great place to start.</p>
|
||||
<p>If you want to understand how things work under the hood in even more detail,
|
||||
see Chapters <a href="https://rust-lang.github.io/async-book/02_execution/01_chapter.html">2</a><!-- ignore --> and
|
||||
<a href="https://rust-lang.github.io/async-book/04_pinning/01_chapter.html">4</a><!-- ignore --> of
|
||||
<a href="https://rust-lang.github.io/async-book/"><em>Asynchronous Programming in Rust</em></a>.</p>
|
||||
</section>
|
||||
<h3 id="the-stream-trait"><a class="header" href="#the-stream-trait">The <code>Stream</code> Trait</a></h3>
|
||||
<p>Now that you have a deeper grasp on the <code>Future</code>, <code>Pin</code>, and <code>Unpin</code> traits, we
|
||||
can turn our attention to the <code>Stream</code> trait. As you learned earlier in the
|
||||
chapter, streams are similar to asynchronous iterators. Unlike <code>Iterator</code> and
|
||||
<code>Future</code>, however, <code>Stream</code> has no definition in the standard library as of
|
||||
this writing, but there <em>is</em> a very common definition from the <code>futures</code> crate
|
||||
used throughout the ecosystem.</p>
|
||||
<p>Let’s review the definitions of the <code>Iterator</code> and <code>Future</code> traits before
|
||||
looking at how a <code>Stream</code> trait might merge them together. From <code>Iterator</code>, we
|
||||
have the idea of a sequence: its <code>next</code> method provides an
|
||||
<code>Option<Self::Item></code>. From <code>Future</code>, we have the idea of readiness over time:
|
||||
its <code>poll</code> method provides a <code>Poll<Self::Output></code>. To represent a sequence of
|
||||
items that become ready over time, we define a <code>Stream</code> trait that puts those
|
||||
features together:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span>use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
trait Stream {
|
||||
type Item;
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>
|
||||
) -> Poll<Option<Self::Item>>;
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<p>The <code>Stream</code> trait defines an associated type called <code>Item</code> for the type of the
|
||||
items produced by the stream. This is similar to <code>Iterator</code>, where there may be
|
||||
zero to many items, and unlike <code>Future</code>, where there is always a single
|
||||
<code>Output</code>, even if it’s the unit type <code>()</code>.</p>
|
||||
<p><code>Stream</code> also defines a method to get those items. We call it <code>poll_next</code>, to
|
||||
make it clear that it polls in the same way <code>Future::poll</code> does and produces a
|
||||
sequence of items in the same way <code>Iterator::next</code> does. Its return type
|
||||
combines <code>Poll</code> with <code>Option</code>. The outer type is <code>Poll</code>, because it has to be
|
||||
checked for readiness, just as a future does. The inner type is <code>Option</code>,
|
||||
because it needs to signal whether there are more messages, just as an iterator
|
||||
does.</p>
|
||||
<p>Something very similar to this definition will likely end up as part of Rust’s
|
||||
standard library. In the meantime, it’s part of the toolkit of most runtimes,
|
||||
so you can rely on it, and everything we cover next should generally apply!</p>
|
||||
<p>In the examples we saw in the <a href="ch17-04-streams.html">“Streams: Futures in Sequence”</a><!--
|
||||
ignore --> section, though, we didn’t use <code>poll_next</code> <em>or</em> <code>Stream</code>, but
|
||||
instead used <code>next</code> and <code>StreamExt</code>. We <em>could</em> work directly in terms of the
|
||||
<code>poll_next</code> API by hand-writing our own <code>Stream</code> state machines, of course,
|
||||
just as we <em>could</em> work with futures directly via their <code>poll</code> method. Using
|
||||
<code>await</code> is much nicer, though, and the <code>StreamExt</code> trait supplies the <code>next</code>
|
||||
method so we can do just that:</p>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">#![allow(unused)]
|
||||
</span><span class="boring">fn main() {
|
||||
</span><span class="boring">use std::pin::Pin;
|
||||
</span><span class="boring">use std::task::{Context, Poll};
|
||||
</span><span class="boring">
|
||||
</span><span class="boring">trait Stream {
|
||||
</span><span class="boring"> type Item;
|
||||
</span><span class="boring"> fn poll_next(
|
||||
</span><span class="boring"> self: Pin<&mut Self>,
|
||||
</span><span class="boring"> cx: &mut Context<'_>,
|
||||
</span><span class="boring"> ) -> Poll<Option<Self::Item>>;
|
||||
</span><span class="boring">}
|
||||
</span><span class="boring">
|
||||
</span>trait StreamExt: Stream {
|
||||
async fn next(&mut self) -> Option<Self::Item>
|
||||
where
|
||||
Self: Unpin;
|
||||
|
||||
// other methods...
|
||||
}
|
||||
<span class="boring">}</span></code></pre>
|
||||
<!--
|
||||
TODO: update this if/when tokio/etc. update their MSRV and switch to using async functions
|
||||
in traits, since the lack thereof is the reason they do not yet have this.
|
||||
-->
|
||||
<section class="note" aria-role="note">
|
||||
<p>Note: The actual definition we used earlier in the chapter looks slightly
|
||||
different than this, because it supports versions of Rust that did not yet
|
||||
support using async functions in traits. As a result, it looks like this:</p>
|
||||
<pre><code class="language-rust ignore">fn next(&mut self) -> Next<'_, Self> where Self: Unpin;</code></pre>
|
||||
<p>That <code>Next</code> type is a <code>struct</code> that implements <code>Future</code> and allows us to name
|
||||
the lifetime of the reference to <code>self</code> with <code>Next<'_, Self></code>, so that <code>await</code>
|
||||
can work with this method.</p>
|
||||
</section>
|
||||
<p>The <code>StreamExt</code> trait is also the home of all the interesting methods available
|
||||
to use with streams. <code>StreamExt</code> is automatically implemented for every type
|
||||
that implements <code>Stream</code>, but these traits are defined separately to enable the
|
||||
community to iterate on convenience APIs without affecting the foundational
|
||||
trait.</p>
|
||||
<p>In the version of <code>StreamExt</code> used in the <code>trpl</code> crate, the trait not only
|
||||
defines the <code>next</code> method but also supplies a default implementation of <code>next</code>
|
||||
that correctly handles the details of calling <code>Stream::poll_next</code>. This means
|
||||
that even when you need to write your own streaming data type, you <em>only</em> have
|
||||
to implement <code>Stream</code>, and then anyone who uses your data type can use
|
||||
<code>StreamExt</code> and its methods with it automatically.</p>
|
||||
<p>That’s all we’re going to cover for the lower-level details on these traits. To
|
||||
wrap up, let’s consider how futures (including streams), tasks, and threads all
|
||||
fit together!</p>
|
||||
</body>
|
||||
</html>
|
||||
112
ch17/ch17-06-futures-tasks-threads.html
Normal file
112
ch17/ch17-06-futures-tasks-threads.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Futures, Tasks, and Threads</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 id="putting-it-all-together-futures-tasks-and-threads"><a class="header" href="#putting-it-all-together-futures-tasks-and-threads">Putting It All Together: Futures, Tasks, and Threads</a></h2>
|
||||
<p>As we saw in <a href="http://localhost:3000/ch16-00-concurrency.html">Chapter 16</a><!-- ignore -->, threads provide one approach to
|
||||
concurrency. We’ve seen another approach in this chapter: using async with
|
||||
futures and streams. If you’re wondering when to choose one method over the other,
|
||||
the answer is: it depends! And in many cases, the choice isn’t threads <em>or</em>
|
||||
async but rather threads <em>and</em> async.</p>
|
||||
<p>Many operating systems have supplied threading-based concurrency models for
|
||||
decades now, and many programming languages support them as a result. However,
|
||||
these models are not without their tradeoffs. On many operating systems, they
|
||||
use a fair bit of memory for each thread. Threads are also only an option when
|
||||
your operating system and hardware support them. Unlike mainstream desktop and
|
||||
mobile computers, some embedded systems don’t have an OS at all, so they also
|
||||
don’t have threads.</p>
|
||||
<p>The async model provides a different—and ultimately complementary—set of
|
||||
tradeoffs. In the async model, concurrent operations don’t require their own
|
||||
threads. Instead, they can run on tasks, as when we used <code>trpl::spawn_task</code> to
|
||||
kick off work from a synchronous function in the streams section. A task is
|
||||
similar to a thread, but instead of being managed by the operating system, it’s
|
||||
managed by library-level code: the runtime.</p>
|
||||
<p>There’s a reason the APIs for spawning threads and spawning tasks are so
|
||||
similar. Threads act as a boundary for sets of synchronous operations;
|
||||
concurrency is possible <em>between</em> threads. Tasks act as a boundary for sets of
|
||||
<em>asynchronous</em> operations; concurrency is possible both <em>between</em> and <em>within</em>
|
||||
tasks, because a task can switch between futures in its body. Finally, futures
|
||||
are Rust’s most granular unit of concurrency, and each future may represent a
|
||||
tree of other futures. The runtime—specifically, its executor—manages tasks,
|
||||
and tasks manage futures. In that regard, tasks are similar to lightweight,
|
||||
runtime-managed threads with added capabilities that come from being managed by
|
||||
a runtime instead of by the operating system.</p>
|
||||
<p>This doesn’t mean that async tasks are always better than threads (or vice
|
||||
versa). Concurrency with threads is in some ways a simpler programming model
|
||||
than concurrency with <code>async</code>. That can be a strength or a weakness. Threads are
|
||||
somewhat “fire and forget”; they have no native equivalent to a future, so they
|
||||
simply run to completion without being interrupted except by the operating
|
||||
system itself.</p>
|
||||
<p>And it turns out that threads and tasks often work
|
||||
very well together, because tasks can (at least in some runtimes) be moved
|
||||
around between threads. In fact, under the hood, the runtime we’ve been
|
||||
using—including the <code>spawn_blocking</code> and <code>spawn_task</code> functions—is multithreaded
|
||||
by default! Many runtimes use an approach called <em>work stealing</em> to
|
||||
transparently move tasks around between threads, based on how the threads are
|
||||
currently being utilized, to improve the system’s overall performance. That
|
||||
approach actually requires threads <em>and</em> tasks, and therefore futures.</p>
|
||||
<p>When thinking about which method to use when, consider these rules of thumb:</p>
|
||||
<ul>
|
||||
<li>If the work is <em>very parallelizable</em> (that is, CPU-bound), such as processing
|
||||
a bunch of data where each part can be processed separately, threads are a
|
||||
better choice.</li>
|
||||
<li>If the work is <em>very concurrent</em> (that is, I/O-bound), such as handling
|
||||
messages from a bunch of different sources that may come in at different
|
||||
intervals or different rates, async is a better choice.</li>
|
||||
</ul>
|
||||
<p>And if you need both parallelism and concurrency, you don’t have to choose
|
||||
between threads and async. You can use them together freely, letting each
|
||||
play the part it’s best at. For example, Listing 17-25 shows a fairly common
|
||||
example of this kind of mix in real-world Rust code.</p>
|
||||
<figure class="listing" id="listing-17-25">
|
||||
<span class="file-name">Filename: src/main.rs</span>
|
||||
<pre class="playground"><code class="language-rust edition2024"><span class="boring">extern crate trpl; // for mdbook test
|
||||
</span><span class="boring">
|
||||
</span>use std::{thread, time::Duration};
|
||||
|
||||
fn main() {
|
||||
let (tx, mut rx) = trpl::channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
for i in 1..11 {
|
||||
tx.send(i).unwrap();
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
});
|
||||
|
||||
trpl::block_on(async {
|
||||
while let Some(message) = rx.recv().await {
|
||||
println!("{message}");
|
||||
}
|
||||
});
|
||||
}</code></pre>
|
||||
<figcaption><a href="#listing-17-25">Listing 17-25</a>: Sending messages with blocking code in a thread and awaiting the messages in an async block</figcaption>
|
||||
</figure>
|
||||
<p>We begin by creating an async channel, then spawning a thread that takes
|
||||
ownership of the sender side of the channel using the <code>move</code> keyword. Within
|
||||
the thread, we send the numbers 1 through 10, sleeping for a second between
|
||||
each. Finally, we run a future created with an async block passed to
|
||||
<code>trpl::block_on</code> just as we have throughout the chapter. In that future, we
|
||||
await those messages, just as in the other message-passing examples we have
|
||||
seen.</p>
|
||||
<p>To return to the scenario we opened the chapter with, imagine running a set of
|
||||
video encoding tasks using a dedicated thread (because video encoding is
|
||||
compute-bound) but notifying the UI that those operations are done with an
|
||||
async channel. There are countless examples of these kinds of combinations in
|
||||
real-world use cases.</p>
|
||||
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
|
||||
<p>This isn’t the last you’ll see of concurrency in this book. The project in
|
||||
<a href="../ch21/ch21-00-final-project-a-web-server.html">Chapter 21</a><!-- ignore --> will apply these concepts in a more realistic
|
||||
situation than the simpler examples discussed here and compare problem-solving
|
||||
with threading versus tasks and futures more directly.</p>
|
||||
<p>No matter which of these approaches you choose, Rust gives you the tools you
|
||||
need to write safe, fast, concurrent code—whether for a high-throughput web
|
||||
server or an embedded operating system.</p>
|
||||
<p>Next, we’ll talk about idiomatic ways to model problems and structure solutions
|
||||
as your Rust programs get bigger. In addition, we’ll discuss how Rust’s idioms
|
||||
relate to those you might be familiar with from object-oriented programming.</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user