Files
docs-rust/ch17/ch17-01-futures-and-syntax.html
2026-06-22 21:27:36 +05:30

438 lines
28 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 Rusts
<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 youre familiar with those languages, you
may notice some significant differences in how Rust handles the syntax. Thats
for good reason, as well 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 well see
throughout this chapter return types with their own implementations of
<code>Future</code>. Well 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 lets write our first async program: a
little web scraper. Well 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 dont worry—well 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, weve 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
youll 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 its 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 its 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>.
Youll be able to see what crate each re-export comes from, and weve 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. Well build a little command line tool that fetches two web pages,
pulls the <code>&lt;title&gt;</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>Lets 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>&lt;title&gt;</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: &amp;str) -&gt; Option&lt;String&gt; {
let response = trpl::get(url).await;
let response_text = response.text().await;
Html::parse(&amp;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 dont do anything until you ask them to with the <code>await</code> keyword.
(In fact, Rust will show a compiler warning if you dont 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 its 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. Its also different from how many other languages
approach async. But its 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>, well get the first <code>&lt;title&gt;</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&lt;ElementRef&gt;</code>. Finally, we use the
<code>Option::map</code> method, which lets us work with the item in the <code>Option</code> if its
present, and do nothing if it isnt. (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&lt;String&gt;</code>.</p>
<p>Notice that Rusts <code>await</code> keyword goes <em>after</em> the expression youre awaiting,
not before it. That is, its a <em>postfix</em> keyword. This may differ from what
youre used to if youve 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: &amp;str) -&gt; Option&lt;String&gt; {
</span> let response_text = trpl::get(url).await.text().await;
<span class="boring"> Html::parse(&amp;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, lets talk a little more about what weve
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 functions 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: &amp;str) -&gt; impl Future&lt;Output = Option&lt;String&gt;&gt; {
async move {
let text = trpl::get(url).await.text().await;
Html::parse(&amp;text)
.select_first("title")
.map(|title| title.inner_html())
}
}
<span class="boring">}</span></code></pre>
<p>Lets 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&lt;String&gt;</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&lt;String&gt;</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. (Well 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, well get the title for a single page, shown in Listing 17-3.
Unfortunately, this code doesnt 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&lt;String&gt; = std::env::args().collect();
let url = &amp;args[1];
match page_title(url).await {
Some(title) =&gt; println!("The title for {url} was {title}"),
None =&gt; println!("{url} had no title"),
}
}
<span class="boring">
</span><span class="boring">async fn page_title(url: &amp;str) -&gt; Option&lt;String&gt; {
</span><span class="boring"> let response_text = trpl::get(url).await.text().await;
</span><span class="boring"> Html::parse(&amp;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&lt;String&gt;</code>, we use a
<code>match</code> expression to print different messages to account for whether the page
had a <code>&lt;title&gt;</code>.</p>
<p>The only place we can use the <code>await</code> keyword is in async functions or blocks,
and Rust wont 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`
--&gt; 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> cant 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
programs <code>main</code> function can <em>initialize</em> a runtime, but its not a runtime
<em>itself</em>. (Well 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, well 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 thats used to run
the future passed in (the <code>trpl</code> crates <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&lt;String&gt;</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), well be doing more than just one async
function call, so instead well 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&lt;String&gt; = std::env::args().collect();
trpl::block_on(async {
let url = &amp;args[1];
match page_title(url).await {
Some(title) =&gt; println!("The title for {url} was {title}"),
None =&gt; println!("{url} had no title"),
}
})
}
<span class="boring">
</span><span class="boring">async fn page_title(url: &amp;str) -&gt; Option&lt;String&gt; {
</span><span class="boring"> let response_text = trpl::get(url).await.text().await;
</span><span class="boring"> Html::parse(&amp;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, lets 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 its
ready to try advancing the first one again. This is an invisible state machine,
as if youd 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&lt;'a&gt; {
Initial { url: &amp;'a str },
GetAwaitPoint { url: &amp;'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. Well 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 its 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 lets 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&lt;String&gt; = std::env::args().collect();
trpl::block_on(async {
let title_fut_1 = page_title(&amp;args[1]);
let title_fut_2 = page_title(&amp;args[2]);
let (url, maybe_title) =
match trpl::select(title_fut_1, title_fut_2).await {
Either::Left(left) =&gt; left,
Either::Right(right) =&gt; right,
};
println!("{url} returned first");
match maybe_title {
Some(title) =&gt; println!("Its page title was: '{title}'"),
None =&gt; println!("It had no title."),
}
})
}
async fn page_title(url: &amp;str) -&gt; (&amp;str, Option&lt;String&gt;) {
let response_text = trpl::get(url).await.text().await;
let title = Html::parse(&amp;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 dont
do anything yet, because futures are lazy and we havent 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> crates <code>select</code>
function can do a lot of things that the <code>trpl::select</code> function cant, but
it also has some additional complexity that we can skip over for now.</p>
</section>
<p>Either future can legitimately “win,” so it doesnt make sense to return a
<code>Result</code>. Instead, <code>trpl::select</code> returns a type we havent 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&lt;A, B&gt; {
Left(A),
Right(B),
}
<span class="boring">}</span></code></pre>
<p>The <code>select</code> function returns <code>Left</code> with that futures output if the first
argument wins, and <code>Right</code> with the second future arguments 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>&lt;title&gt;</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>&lt;title&gt;</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, youve 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>