feat: added cleanscript
This commit is contained in:
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>
|
||||
Reference in New Issue
Block a user