Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


x-i18n: generated_at: “2026-03-01T14:44:26Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: dff0af8b0374b505748ded80c3f7656742a8d3a219a6e276ce69478e82f06633 source_path: ch17-01-futures-and-syntax.md workflow: 16

Future 和异步语法 (Futures and the Async Syntax)

Rust 异步编程的关键元素是“future”以及 Rust 的 asyncawait 关键字。

The key elements of asynchronous programming in Rust are futures and Rust’s async and await keywords.

一个 “future” 是一个现在可能尚未就绪,但在未来某个时刻会变得就绪的值。(这个概念在许多语言中都有出现,有时以其他名称出现,如 taskpromise。)Rust 提供了一个 Future 特征作为构建块,以便不同的异步操作可以用不同的数据结构实现,但具有共同的接口。在 Rust 中,future 是实现了 Future 特征的类型。每个 future 都持有其自身关于已取得的进度以及“就绪”意味着什么的信息。

A future 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 task or promise.) Rust provides a Future 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 Future trait. Each future holds its own information about the progress that has been made and what “ready” means.

你可以将 async 关键字应用于代码块和函数,以指定它们可以被中断和恢复。在异步代码块或异步函数中,你可以使用 await 关键字来“等待一个 future (await a future)”(即,等待它变得就绪)。在异步代码块或函数中等待一个 future 的任何点,都是该代码块或函数暂停和恢复的潜在位置。向 future 检查其值是否可用的过程称为“轮询 (polling)”。

You can apply the async 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 await keyword to await a future (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 polling.

其他一些语言(如 C# 和 JavaScript)也使用 asyncawait 关键字进行异步编程。如果你熟悉这些语言,你可能会注意到 Rust 处理该语法的方式存在一些显著差异。这正如我们将看到的,是有充分理由的!

Some other languages, such as C# and JavaScript, also use async and await 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!

在编写异步 Rust 代码时,我们大多数时候使用 asyncawait 关键字。Rust 将它们编译成使用 Future 特征的等效代码,就像它将 for 循环编译成使用 Iterator 特征的等效代码一样。不过,由于 Rust 提供了 Future 特征,你也可以在需要时为自己的数据类型实现它。我们在本章中看到的许多函数都会返回具有其自身 Future 实现的类型。我们将在本章末尾回到该特征的定义并深入研究它的工作原理,但这些细节已足以让我们继续前进。

When writing async Rust, we use the async and await keywords most of the time. Rust compiles them into equivalent code using the Future trait, much as it compiles for loops into equivalent code using the Iterator trait. Because Rust provides the Future 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 Future. 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.

这一切可能感觉有点抽象,所以让我们编写我们的第一个异步程序:一个小型的 Web 爬虫。我们将从命令行传入两个 URL,并发获取它们,并返回其中第一个完成的那个的结果。这个例子会有相当一部分新语法,但不用担心——我们将边做边解释你需要知道的一切。

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.

我们的第一个异步程序 (Our First Async Program)

Our First Async Program

为了让本章的重点放在学习异步编程上,而不是应付生态系统的各个部分,我们创建了 trpl crate (trpl 是 “The Rust Programming Language” 的缩写)。它重新导出了你所需的所有类型、特征和函数,主要来自 futurestokio crate。 futures crate 是 Rust 进行异步代码实验的官方场所,它实际上是 Future 特征最初被设计出来的地方。Tokio 是当今 Rust 中使用最广泛的异步运行时,特别是在 Web 应用程序中。还有其他优秀的运行时,它们可能更适合你的用途。我们在 trpl 的底层使用 tokio crate,因为它经过了良好的测试且被广泛使用。

To keep the focus of this chapter on learning async rather than juggling parts of the ecosystem, we’ve created the trpl crate (trpl is short for “The Rust Programming Language”). It re-exports all the types, traits, and functions you’ll need, primarily from the futures and tokio crates. The futures crate is an official home for Rust experimentation for async code, and it’s actually where the Future 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 tokio crate under the hood for trpl because it’s well tested and widely used.

在某些情况下, trpl 还对原始 API 进行了重命名或包装,以便让你专注于本章相关的细节。如果你想了解这个 crate 的作用,我们鼓励你查看它的源代码。你将能够看到每个重新导出源自哪个 crate,并且我们留下了大量的注释来解释这个 crate 的作用。

In some cases, trpl 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 its source code. You’ll be able to see what crate each re-export comes from, and we’ve left extensive comments explaining what the crate does.

创建一个名为 hello-async 的新二进制项目,并将 trpl crate 添加为依赖项:

Create a new binary project named hello-async and add the trpl crate as a dependency:

$ cargo new hello-async
$ cd hello-async
$ cargo add trpl

现在我们可以使用 trpl 提供的各个部分来编写我们的第一个异步程序。我们将构建一个小型的命令行工具,用于获取两个网页,从每个网页中提取 <title> 元素,并打印出在那整个过程中第一个完成的页面的标题。

Now we can use the various pieces provided by trpl to write our first async program. We’ll build a little command line tool that fetches two web pages, pulls the <title> element from each, and prints out the title of whichever page finishes that whole process first.

定义 page_title 函数 (Defining the page_title Function)

让我们从编写一个函数开始,它接收一个页面 URL 作为参数,向其发起请求,并返回 <title> 元素的文本(见示例 17-1)。

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 <title> element (see Listing 17-1).

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch17-async-await/listing-17-01/src/main.rs:all}}
}

首先,我们定义一个名为 page_title 的函数,并用 async 关键字标记它。然后我们使用 trpl::get 函数获取传入的任何 URL,并添加 await 关键字来等待响应。为了获取 response 的文本,我们调用它的 text 方法,并再次使用 await 关键字等待它。这两个步骤都是异步的。对于 get 函数,我们必须等待服务器发回其响应的第一部分,其中将包括 HTTP 标头、Cookie 等,并且可以与响应体分开交付。特别是如果正文非常大,那么全部送达可能需要一些时间。因为我们必须等待响应的“全部”送达,所以 text 方法也是异步的。

First, we define a function named page_title and mark it with the async keyword. Then we use the trpl::get function to fetch whatever URL is passed in and add the await keyword to await the response. To get the text of the response, we call its text method and once again await it with the await keyword. Both of these steps are asynchronous. For the get 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 entirety of the response to arrive, the text method is also async.

我们必须显式地等待这两个 future,因为 Rust 中的 future 是“惰性的 (lazy)”:除非你使用 await 关键字要求它们这样做,否则它们什么都不做。(事实上,如果你不使用 future,Rust 会显示编译器警告。)这可能会让你想起第 13 章“使用迭代器处理一系列项”部分中关于迭代器的讨论。除非你调用它们的 next 方法——无论是直接调用,还是通过使用 for 循环或底层使用 next 的方法(如 map),否则迭代器什么都不做。同样,除非你显式要求,否则 future 什么都不做。这种惰性允许 Rust 避免运行异步代码,直到真正需要它为止。

We have to explicitly await both of these futures, because futures in Rust are lazy: they don’t do anything until you ask them to with the await 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 “Processing a Series of Items with Iterators” section in Chapter 13. Iterators do nothing unless you call their next method—whether directly or by using for loops or methods such as map that use next 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.

注意:这与我们在第 16 章“使用 spawn 创建新线程”部分中看到的行为不同,在那里我们传递给另一个线程的闭包立即开始运行。这也与许多其他语言处理异步的方式不同。但正如迭代器一样,这对 Rust 能够提供其性能保证至关重要。

Note: This is different from the behavior we saw when using thread::spawn in the “Creating a New Thread with spawn” 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.

一旦我们有了 response_text ,我们就可以使用 Html::parse 将其解析为 Html 类型的实例。现在我们有了一个可以用来将 HTML 处理为更丰富数据结构的数据类型,而不是原始字符串。特别地,我们可以使用 select_first 方法来查找给定 CSS 选择器的第一个实例。通过传入字符串 "title" ,我们将获得文档中的第一个 <title> 元素(如果存在的话)。因为可能没有任何匹配的元素, select_first 返回一个 Option<ElementRef> 。最后,我们使用 Option::map 方法,它让我们能在元素存在时处理它,在不存在时什么都不做。(我们也可以在这里使用 match 表达式,但 map 更加惯用。)在我们提供给 map 的函数体中,我们对 title 调用 inner_html 以获取其内容,内容是一个 String 。到最后,我们就得到了一个 Option<String>

Once we have response_text, we can parse it into an instance of the Html type using Html::parse. 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 select_first method to find the first instance of a given CSS selector. By passing the string "title", we’ll get the first <title> element in the document, if there is one. Because there may not be any matching element, select_first returns an Option<ElementRef>. Finally, we use the Option::map method, which lets us work with the item in the Option if it’s present, and do nothing if it isn’t. (We could also use a match expression here, but map is more idiomatic.) In the body of the function we supply to map, we call inner_html on the title to get its content, which is a String. When all is said and done, we have an Option<String>.

请注意,Rust 的 await 关键字位于你正在等待的表达式“之后”,而不是之前。也就是说,它是一个“后缀 (postfix)”关键字。如果你在其他语言中使用过 async ,这可能与你的习惯不同,但在 Rust 中,这使得链式调用方法处理起来更加美观。因此,我们可以更改 page_title 的主体,将 trpl::gettext 函数调用链接在一起,中间用 await 分隔,如示例 17-2 所示。

Notice that Rust’s await keyword goes after the expression you’re awaiting, not before it. That is, it’s a postfix keyword. This may differ from what you’re used to if you’ve used async 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 page_title to chain the trpl::get and text function calls together with await between them, as shown in Listing 17-2.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch17-async-await/listing-17-02/src/main.rs:chaining}}
}

至此,我们成功编写了我们的第一个异步函数!在我们在 main 中添加一些代码来调用它之前,让我们先多谈谈我们所写的内容以及它的含义。

With that, we have successfully written our first async function! Before we add some code in main to call it, let’s talk a little more about what we’ve written and what it means.

当 Rust 看到一个被标记为 async 关键字的“代码块 (block)”时,它会将其编译成一个实现 Future 特征的独特的、匿名的数据类型。当 Rust 看到一个被标记为 async 的“函数 (function)”时,它会将其编译成一个非异步函数,其主体是一个异步代码块。异步函数的返回类型是编译器为该异步代码块创建的匿名数据类型的类型。

When Rust sees a block marked with the async keyword, it compiles it into a unique, anonymous data type that implements the Future trait. When Rust sees a function marked with async, 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.

因此,编写 async fn 相当于编写一个返回返回类型之 “future” 的函数。对于编译器来说,示例 17-1 中的 async fn page_title 函数定义大致相当于如下定义的非异步函数:

Thus, writing async fn is equivalent to writing a function that returns a future of the return type. To the compiler, a function definition such as the async fn page_title in Listing 17-1 is roughly equivalent to a non-async function defined like this:

#![allow(unused)]
fn main() {
extern crate trpl; // required for mdbook test
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())
    }
}
}

让我们逐步了解转换后版本的每个部分:

  • 它使用了我们在第 10 章“特征作为参数”部分讨论过的 impl Trait 语法。

  • 返回值实现了 Future 特征,带有一个关联类型 Output 。请注意, Output 类型是 Option<String> ,这与 async fn 版本的 page_title 的原始返回类型相同。

  • 原始函数体中调用的所有代码都被包装在一个 async move 块中。请记住,代码块是表达式。这整个块就是从函数返回的表达式。

  • 如前所述,这个异步块产生一个类型为 Option<String> 的值。该值与返回类型中的 Output 类型相匹配。这就像你见过的其他代码块一样。

  • 由于新函数体使用 url 参数的方式,它是一个 async move 块。(我们将在本章稍后更深入地讨论 asyncasync move 。)

  • It uses the impl Trait syntax we discussed back in Chapter 10 in the “Traits as Parameters” section.

  • The returned value implements the Future trait with an associated type of Output. Notice that the Output type is Option<String>, which is the same as the original return type from the async fn version of page_title.

  • All of the code called in the body of the original function is wrapped in an async move block. Remember that blocks are expressions. This whole block is the expression returned from the function.

  • This async block produces a value with the type Option<String>, as just described. That value matches the Output type in the return type. This is just like other blocks you have seen.

  • The new function body is an async move block because of how it uses the url parameter. (We’ll talk much more about async versus async move later in the chapter.)

现在我们可以在 main 中调用 page_title 了。

Now we can call page_title in main.

通过运行时执行异步函数 (Executing an Async Function with a Runtime)

Executing an Async Function with a Runtime

首先,我们将获取单个页面的标题,如示例 17-3 所示。不幸的是,这段代码目前还无法通过编译。

To start, we’ll get the title for a single page, shown in Listing 17-3. Unfortunately, this code doesn’t compile yet.

{{#rustdoc_include ../listings/ch17-async-await/listing-17-03/src/main.rs:main}}

我们遵循了我们在第 12 章“接收命令行参数”部分中用于获取命令行参数的相同模式。然后我们将 URL 参数传递给 page_title 并等待结果。因为 future 产生的值是一个 Option<String> ,所以我们使用 match 表达式来打印不同的消息,以考虑到页面是否具有 <title>

We follow the same pattern we used to get command line arguments in the “Accepting Command Line Arguments” section in Chapter 12. Then we pass the URL argument to page_title and await the result. Because the value produced by the future is an Option<String>, we use a match expression to print different messages to account for whether the page had a <title>.

我们唯一可以使用 await 关键字的地方是在异步函数或块中,而 Rust 不允许我们将特殊的 main 函数标记为 async

The only place we can use the await keyword is in async functions or blocks, and Rust won’t let us mark the special main function as async.

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`

(错误[E0752]:不允许 main 函数为 async

main 不能被标记为 async 的原因是异步代码需要一个“运行时 (runtime)”:一个管理执行异步代码细节的 Rust crate。程序的 main 函数可以“初始化”一个运行时,但它“本身”不是一个运行时。(我们稍后会看到为什么是这种情况。)每个执行异步代码的 Rust 程序都至少有一个设置运行未来代码的地方。

The reason main can’t be marked async is that async code needs a runtime: a Rust crate that manages the details of executing asynchronous code. A program’s main function can initialize a runtime, but it’s not a runtime itself. (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.

大多数支持异步的语言都会绑定一个运行时,但 Rust 没有。相反,有许多不同的异步运行时可供选择,每个运行时都做出了适合其目标用例的不同权衡。例如,具有多个 CPU 核心和大量 RAM 的高吞吐量 Web 服务器,与具有单个核心、少量 RAM 且没有堆分配能力的微控制器有着非常不同的需求。提供这些运行时的 crate 通常也会提供常用功能的异步版本,如文件或网络 I/O。

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.

在这里,以及本章的其余部分,我们将使用 trpl crate 中的 block_on 函数,它接收一个 future 作为参数,并阻塞当前线程直到该 future 运行完成。在幕后,调用 block_on 会使用 tokio crate 设置一个运行时,该运行时用于运行传入的 future( trpl crate 的 block_on 行为与其他运行时 crate 的 block_on 函数类似)。一旦 future 完成, block_on 就会返回该 future 产生的任何值。

Here, and throughout the rest of this chapter, we’ll use the block_on function from the trpl crate, which takes a future as an argument and blocks the current thread until this future runs to completion. Behind the scenes, calling block_on sets up a runtime using the tokio crate that’s used to run the future passed in (the trpl crate’s block_on behavior is similar to other runtime crates’ block_on functions). Once the future completes, block_on returns whatever value the future produced.

我们可以将 page_title 返回的 future 直接传递给 block_on ,并在它完成后,像我们在示例 17-3 中尝试做的那样对生成的 Option<String> 进行匹配。然而,对于本章中的大多数示例(以及现实世界中的大多数异步代码),我们将执行不止一个异步函数调用,因此我们将传递一个 async 块并显式等待 page_title 调用的结果,如示例 17-4 所示。

We could pass the future returned by page_title directly to block_on and, once it completed, we could match on the resulting Option<String> 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 async block and explicitly await the result of the page_title call, as in Listing 17-4.

{{#rustdoc_include ../listings/ch17-async-await/listing-17-04/src/main.rs:run}}

当我们运行这段代码时,我们得到了最初预期的行为:

When we run this code, we get the behavior we expected initially:

$ 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

呼——我们终于有了一些可以工作的异步代码!但在我们添加让两个网站相互竞争的代码之前,让我们简要地将注意力转回到 future 的工作原理上。

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.

每一个“等待点 (await point)”——也就是说,代码中每个使用 await 关键字的地方——都代表一个将控制权交还给运行时的位置。为了做到这一点,Rust 需要跟踪异步块中涉及的状态,以便运行时可以启动一些其他工作,然后在准备好再次尝试推进第一个工作时回来。这是一个无形的状态机,就像你编写了一个如下所示的枚举来保存每个等待点的当前状态一样:

Each await point—that is, every place where the code uses the await 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:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch17-async-await/no-listing-state-machine/src/lib.rs:enum}}
}

然而,手动编写在每个状态之间转换的代码将是乏味且容易出错的,特别是当你以后需要向代码添加更多功能和更多状态时。幸运的是,Rust 编译器会自动为异步代码创建和管理状态机数据结构。通常围绕数据结构的所有权和借用规则仍然适用,令人高兴的是,编译器还为我们处理了这些检查并提供了有用的错误消息。我们将在本章稍后研究其中的一些例子。

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.

最终,必须有东西来执行这个状态机,而那个东西就是运行时。(这就是为什么你在研究运行时的时候可能会遇到关于“执行器 (executors)”的提及:执行器是运行时中负责执行异步代码的部分。)

Ultimately, something has to execute this state machine, and that something is a runtime. (This is why you may come across mentions of executors when looking into runtimes: an executor is the part of a runtime responsible for executing the async code.)

现在你可以理解为什么编译器在示例 17-3 中阻止我们将 main 本身设为异步函数了。如果 main 是一个异步函数,那么就需要其他东西来管理 main 返回的任何 future 的状态机,但 main 是程序的起点!相反,我们在 main 中调用了 trpl::block_on 函数来设置运行时并运行由 async 块返回的 future,直到它完成。

Now you can see why the compiler stopped us from making main itself an async function back in Listing 17-3. If main were an async function, something else would need to manage the state machine for whatever future main returned, but main is the starting point for the program! Instead, we called the trpl::block_on function in main to set up a runtime and run the future returned by the async block until it’s done.

注意:一些运行时提供了宏,以便你可以编写异步的 main 函数。这些宏将 async fn main() { ... } 重写为正常的 fn main ,其作用与我们在示例 17-4 中手动执行的操作相同:调用一个像 trpl::block_on 一样运行 future 直至完成的函数。

Note: Some runtimes provide macros so you can write an async main function. Those macros rewrite async fn main() { … } to be a normal fn main, which does the same thing we did by hand in Listing 17-4: call a function that runs a future to completion the way trpl::block_on does.

现在让我们把这些碎片拼凑起来,看看我们如何编写并发代码。

Now let’s put these pieces together and see how we can write concurrent code.

让两个 URL 并发竞争 (Racing Two URLs Against Each Other Concurrently)

Racing Two URLs Against Each Other Concurrently

在示例 17-5 中,我们对从命令行传入的两个不同 URL 调用 page_title ,并通过选择先完成的那个 future 来让它们竞争。

In Listing 17-5, we call page_title with two different URLs passed in from the command line and race them by selecting whichever future finishes first.

{{#rustdoc_include ../listings/ch17-async-await/listing-17-05/src/main.rs:all}}

我们首先为用户提供的每个 URL 调用 page_title 。我们将生成的 future 保存为 title_fut_1title_fut_2 。请记住,这些目前还没有执行任何操作,因为 future 是惰性的,我们还没有 await 它们。然后我们将这些 future 传递给 trpl::select ,它返回一个值以指示传递给它的哪些 future 先完成。

We begin by calling page_title for each of the user-supplied URLs. We save the resulting futures as title_fut_1 and title_fut_2. Remember, these don’t do anything yet, because futures are lazy and we haven’t yet awaited them. Then we pass the futures to trpl::select, which returns a value to indicate which of the futures passed to it finishes first.

注意:在幕后, trpl::select 是建立在 futures crate 中定义的更通用的 select 函数之上的。 futures crate 的 select 函数可以做很多 trpl::select 函数做不到的事情,但它也具有一些我们目前可以略过的额外复杂性。

Note: Under the hood, trpl::select is built on a more general select function defined in the futures crate. The futures crate’s select function can do a lot of things that the trpl::select function can’t, but it also has some additional complexity that we can skip over for now.

任何一个 future 都可以名正言顺地“获胜”,所以返回 Result 没有意义。相反, trpl::select 返回一个我们以前没见过的类型 trpl::EitherEither 类型在某种程度上类似于 Result ,因为它有两种情况。不过与 Result 不同的是, Either 中没有成功或失败的概念。相反,它使用 LeftRight 来指示“两者之一”:

Either future can legitimately “win,” so it doesn’t make sense to return a Result. Instead, trpl::select returns a type we haven’t seen before, trpl::Either. The Either type is somewhat similar to a Result in that it has two cases. Unlike Result, though, there is no notion of success or failure baked into Either. Instead, it uses Left and Right to indicate “one or the other”:

#![allow(unused)]
fn main() {
enum Either<A, B> {
    Left(A),
    Right(B),
}
}

如果第一个参数获胜, select 函数返回带有该 future 输出的 Left ;如果第二个 future 参数获胜,则返回带有其输出的 Right 。这与调用函数时参数出现的顺序一致:第一个参数在第二个参数的左侧。

The select function returns Left with that future’s output if the first argument wins, and Right with the second future argument’s output if that 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.

我们还更新了 page_title 以返回传入的相同 URL。这样,如果先返回的页面没有我们可以解析的 <title> ,我们仍然可以打印一条有意义的消息。有了这些可用的信息,我们最后更新了 println! 输出,以指示哪个 URL 先完成以及该 URL 对应网页的 <title> 是什么(如果有的话)。

We also update page_title to return the same URL passed in. That way, if the page that returns first does not have a <title> we can resolve, we can still print a meaningful message. With that information available, we wrap up by updating our println! output to indicate both which URL finished first and what, if any, the <title> is for the web page at that URL.

你现在已经构建了一个小型可运行的 Web 爬虫了!选几个 URL 并运行这个命令行工具。你可能会发现某些网站总是比其他网站快,而在其他情况下,较快的网站在每次运行中都不尽相同。更重要的是,你已经学习了使用 future 的基础知识,所以现在我们可以深入研究我们可以用异步做些什么。

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.