You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trpl-zh-cn/ch17-01-futures-and-syntax....

481 lines
48 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Futures 和 async 语法 - Rust 程序设计语言 简体中文版</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon-de23e50b.svg">
<link rel="shortcut icon" href="favicon-8114d1fc.png">
<link rel="stylesheet" href="css/variables-8adf115d.css">
<link rel="stylesheet" href="css/general-2459343d.css">
<link rel="stylesheet" href="css/chrome-ae938929.css">
<link rel="stylesheet" href="css/print-9e4910d8.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="fonts/fonts-9644e21d.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="mdbook-highlight-css" href="highlight-493f70e1.css">
<link rel="stylesheet" id="mdbook-tomorrow-night-css" href="tomorrow-night-4c0ae647.css">
<link rel="stylesheet" id="mdbook-ayu-highlight-css" href="ayu-highlight-3fdfc3ac.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="ferris-d33b75bf.css">
<link rel="stylesheet" href="theme/2018-edition-4e126c62.css">
<link rel="stylesheet" href="theme/semantic-notes-9b5766c0.css">
<link rel="stylesheet" href="theme/listing-cab26221.css">
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "searchindex-e0ffcbc3.js";
</script>
<!-- Start loading toc.js asap -->
<script src="toc-fb31ca2f.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="mdbook-body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="mdbook-sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("mdbook-sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
sidebar_toggle.checked = false;
}
if (sidebar === 'visible') {
sidebar_toggle.checked = true;
} else {
html.classList.remove('sidebar-visible');
}
</script>
<nav id="mdbook-sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
</noscript>
<div id="mdbook-sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="mdbook-page-wrapper" class="page-wrapper">
<div class="page">
<div id="mdbook-menu-bar-hover-placeholder"></div>
<div id="mdbook-menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="mdbook-sidebar-toggle" class="icon-button" for="mdbook-sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="mdbook-sidebar">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg></span>
</label>
<button id="mdbook-theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="mdbook-theme-list">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M371.3 367.1c27.3-3.9 51.9-19.4 67.2-42.9L600.2 74.1c12.6-19.5 9.4-45.3-7.6-61.2S549.7-4.4 531.1 9.6L294.4 187.2c-24 18-38.2 46.1-38.4 76.1L371.3 367.1zm-19.6 25.4l-116-104.4C175.9 290.3 128 339.6 128 400c0 3.9 .2 7.8 .6 11.6c1.8 17.5-10.2 36.4-27.8 36.4H96c-17.7 0-32 14.3-32 32s14.3 32 32 32H240c61.9 0 112-50.1 112-112c0-2.5-.1-5-.2-7.5z"/></svg></span>
</button>
<ul id="mdbook-theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="mdbook-theme-ayu">Ayu</button></li>
</ul>
<button id="mdbook-search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="mdbook-searchbar">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z"/></svg></span>
</button>
</div>
<h1 class="menu-title">Rust 程序设计语言 简体中文版</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<span class=fa-svg id="print-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M128 0C92.7 0 64 28.7 64 64v96h64V64H354.7L384 93.3V160h64V93.3c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0H128zM384 352v32 64H128V384 368 352H384zm64 32h32c17.7 0 32-14.3 32-32V256c0-35.3-28.7-64-64-64H64c-35.3 0-64 28.7-64 64v96c0 17.7 14.3 32 32 32H64v64c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V384zm-16-88c-13.3 0-24-10.7-24-24s10.7-24 24-24s24 10.7 24 24s-10.7 24-24 24z"/></svg></span>
</a>
<a href="https://github.com/KaiserY/trpl-zh-cn/tree/main" title="Git repository" aria-label="Git repository">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg></span>
</a>
</div>
</div>
<div id="mdbook-search-wrapper" class="hidden">
<form id="mdbook-searchbar-outer" class="searchbar-outer">
<div class="search-wrapper">
<input type="search" id="mdbook-searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="mdbook-searchresults-outer" aria-describedby="searchresults-header">
<div class="spinner-wrapper">
<span class=fa-svg id="fa-spin"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z"/></svg></span>
</div>
</div>
</form>
<div id="mdbook-searchresults-outer" class="searchresults-outer hidden">
<div id="mdbook-searchresults-header" class="searchresults-header"></div>
<ul id="mdbook-searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('mdbook-sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('mdbook-sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#mdbook-sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="mdbook-content" class="content">
<main>
<h2 id="future-与-async-语法"><a class="header" href="#future-与-async-语法">Future 与 async 语法</a></h2>
<p><a href="https://github.com/rust-lang/book/blob/0d5a0dd395aba1f82d7e5aaf6dbb59b2b843ad2c/src/ch17-01-futures-and-syntax.md">ch17-01-futures-and-syntax.md</a></p>
<p>Rust 异步编程的关键元素是 <em>futures</em> 和 Rust 的 <code>async</code><code>await</code> 关键字。</p>
<p><em>future</em> 是一个现在也许还没准备好,但会在将来某个时刻准备好的值。(这个概念在很多语言里都存在,只是有时会用 <em>task</em><em>promise</em> 之类的名字。Rust 提供了 <code>Future</code> trait 作为基础构件,让不同的异步操作可以用不同的数据结构来实现,同时又拥有统一的接口。在 Rust 中future 就是那些实现了 <code>Future</code> trait 的类型。每个 future 都保存了自身的进度信息,以及“就绪”到底意味着什么。</p>
<p><code>async</code> 关键字可以用于代码块和函数,表示它们可以被中断和恢复。在 async 块或 async 函数中,你可以使用 <code>await</code> 关键字来 <em>await 一个 future</em>,也就是等待它变为就绪。在 async 块或函数里,每个等待 future 的位置,都是这个块或函数可能暂停并随后恢复的点。检查 future、看看它的值是否已经可用这个过程称为 <em>polling</em>(轮询)。</p>
<p>其他一些语言,例如 C# 和 JavaScript也用 <code>async</code><code>await</code> 关键字进行异步编程。如果你熟悉这些语言,可能会注意到 Rust 在语法处理上存在一些明显差异。我们会看到,这样设计是有充分理由的。</p>
<p>编写异步 Rust 时,大多数时候我们直接使用 <code>async</code><code>await</code> 关键字。Rust 会把它们编译成等价的、基于 <code>Future</code> trait 的代码,就像它把 <code>for</code> 循环编译成基于 <code>Iterator</code> trait 的等价代码一样。不过,既然 Rust 提供了 <code>Future</code> trait你在需要时也可以为自己的数据类型实现它。本章中我们会见到很多函数它们都返回拥有各自 <code>Future</code> 实现的类型。我们会在本章结尾回到这个 trait 的定义,进一步深入理解它的工作原理;不过眼下这些细节已经足够让我们继续前进。</p>
<p>这些内容可能仍然有些抽象,所以我们来写第一个异步程序:一个小型网页抓取器。我们会从命令行传入两个 URL并发地抓取它们然后返回那个最先完成的结果。这个例子会带来不少新语法不过不用担心我们会一路把需要知道的内容都解释清楚。</p>
<h2 id="第一个异步程序"><a class="header" href="#第一个异步程序">第一个异步程序</a></h2>
<p>为了让本章专注于学习 async而不是在生态系统的各种组件之间来回切换我们准备了一个 <code>trpl</code> crate<code>trpl</code> 是 “The Rust Programming Language” 的缩写。它重新导出了本章需要的所有类型、trait 和函数,主要来自 <a href="https://crates.io/crates/futures"><code>futures</code></a><a href="https://tokio.rs"><code>tokio</code></a> crate。<code>futures</code> crate 是 Rust 异步代码实验的官方阵地,<code>Future</code> trait 最初就是在那里设计出来的。Tokio 则是目前 Rust 中使用最广泛的异步运行时async runtime尤其常见于 Web 应用。生态中也还有其他很优秀的运行时,而且它们可能更适合你的实际用途。我们在 <code>trpl</code> 的底层使用 <code>tokio</code>,是因为它经过了充分测试,也足够常用。</p>
<p>在某些场景下,<code>trpl</code> 还会对原始 API 进行重命名或包装,好让你把注意力集中在本章相关的细节上。如果你想了解这个 crate 实际做了什么,我们建议你看看<a href="https://github.com/rust-lang/book/tree/main/packages/trpl">它的源码</a>。你可以从中看到每个重导出项究竟来自哪个 crate我们也留下了很多注释来解释这个 crate 的行为。</p>
<p>创建一个名为 <code>hello-async</code> 的二进制项目并将 <code>trpl</code> crate 作为一个依赖添加:</p>
<pre><code class="language-console">$ cargo new hello-async
$ cd hello-async
$ cargo add trpl
</code></pre>
<p>现在我们可以利用 <code>trpl</code> 提供的各种组件来编写第一个异步程序。我们要构建一个小型命令行工具:抓取两个网页,从各自页面中提取 <code>&lt;title&gt;</code> 元素,然后打印出那个最先完成整套流程的页面标题。</p>
<h3 id="定义-page_title-函数"><a class="header" href="#定义-page_title-函数">定义 page_title 函数</a></h3>
<p>让我们开始编写一个函数,它获取一个网页 URL 作为参数,请求该 URL 并返回标题元素的文本(见示例 17-1</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<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>示例 17-1定义一个 async 函数来获取一个 HTML 页面的标题元素</figcaption>
</figure>
<p>首先,我们定义了一个名为 <code>page_title</code> 的函数,并用 <code>async</code> 关键字标记它。然后使用 <code>trpl::get</code> 函数抓取传入的 URL再用 <code>await</code> 关键字等待响应。为了得到 <code>response</code> 的文本,我们调用它的 <code>text</code> 方法,并再次使用 <code>await</code> 进行等待。这两个步骤都是异步的。对于 <code>get</code> 函数来说,我们必须等待服务器先把响应的第一部分发回来,其中包括 HTTP headers、cookies 等,这些内容可以和响应体分开发送。尤其当响应体很大时,全部数据到达可能要花上一些时间。由于我们必须等待响应<em>完整</em>到达,<code>text</code> 方法自然也是 async 的。</p>
<p>我们必须显式地等待这两个 future因为 Rust 中的 future 是 <em>lazy</em> 的:在你用 <code>await</code> 请求它之前,它什么都不会做。(实际上,如果你创建了 future 却不使用它Rust 还会给出编译器警告。)这大概会让你想起第十三章<a href="ch13-02-iterators.html">“使用迭代器处理元素序列”</a>中的讨论。迭代器只有在你调用 <code>next</code> 方法时才会工作,无论是直接调用,还是通过 <code>for</code> 循环,或者借助像 <code>map</code> 这样底层会调用 <code>next</code> 的方法。future 也是一样,只有你显式要求它运行时,它才会开始工作。这种惰性让 Rust 能够避免在真正需要之前就运行异步代码。</p>
<blockquote>
<p>注意:这和我们在第十六章<a href="ch16-01-threads.html#使用-spawn-创建新线程">“使用 spawn 创建新线程”</a>里看到的 <code>thread::spawn</code> 的行为不同,在那里我们传给新线程的闭包会立刻开始执行。它也和许多其他语言处理 async 的方式不同。但这对于 Rust 提供它一贯的性能保证很重要,正如迭代器也是如此。</p>
</blockquote>
<p>有了 <code>response_text</code> 之后,我们就可以用 <code>Html::parse</code> 把它解析成 <code>Html</code> 类型的实例。这样一来,我们得到的就不再是原始字符串,而是一个可以把 HTML 当作更丰富数据结构来操作的类型。特别是,我们可以用 <code>select_first</code> 方法找到给定 CSS selector 的第一个匹配项。传入字符串 <code>"title"</code> 后,我们就能拿到文档中的第一个 <code>&lt;title&gt;</code> 元素,如果它存在的话。因为也可能根本没有匹配项,所以 <code>select_first</code> 返回的是 <code>Option&lt;ElementRef&gt;</code>。最后,我们使用 <code>Option::map</code> 方法:如果 <code>Option</code> 中有值,它就会对其中的值进行处理;如果没有,就什么都不做。(这里当然也可以使用 <code>match</code> 表达式,不过 <code>map</code> 更符合惯用写法。)在我们传给 <code>map</code> 的闭包里,会对 <code>title</code> 调用 <code>inner_html</code> 来获取其中的内容,它是一个 <code>String</code>。到这里,我们最终得到的就是一个 <code>Option&lt;String&gt;</code></p>
<p>注意Rust 的 <code>await</code> 关键字放在要等待的表达式<em>后面</em>,而不是前面。也就是说,它是一个 <em>postfix keyword</em>(后缀关键字)。如果你在其他语言里用过 async这一点可能和你的习惯不同但在 Rust 中,这种设计会让链式方法调用更易读。因此,我们可以把 <code>page_title</code> 的函数体改写成在 <code>trpl::get</code><code>text</code> 调用之间插入 <code>await</code> 的链式写法,如示例 17-2 所示:</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<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>示例 17-2使用 `await` 关键字的链式调用</figcaption>
</figure>
<p>这样我们就成功编写了第一个异步函数!在我们向 <code>main</code> 加入一些代码调用它之前,让我们再多了解下我们写了什么以及它的意义。</p>
<p>当 Rust 遇到一个 <code>async</code> 关键字标记的代码块时,会将其编译为一个实现了 <code>Future</code> trait 的唯一的、匿名的数据类型。当 Rust 遇到一个被标记为 <code>async</code> 的函数时,会将其编译成一个函数体是异步代码块的非异步函数。异步函数的返回值类型是编译器为异步代码块所创建的匿名数据类型。</p>
<p>因此,编写 <code>async fn</code> 就等同于编写一个返回类型为 <em>future</em> 的函数。当编译器遇到类似示例 17-1 中 <code>async fn page_title</code> 的函数定义时,它等价于以下定义的非异步函数:</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>让我们挨个看一下转换后版本的每一个部分:</p>
<ul>
<li>它使用了之前第十章 <a href="ch10-02-traits.html#使用-trait-作为参数">“trait 作为参数”</a> 部分讨论过的 <code>impl Trait</code> 语法。</li>
<li>它返回的值实现了 <code>Future</code> trait并且这个 trait 有一个关联类型 <code>Output</code>。注意 <code>Output</code> 的类型是 <code>Option&lt;String&gt;</code>,这和 <code>async fn</code> 版本的 <code>page_title</code> 的原始返回类型一致。</li>
<li>原始函数体中的所有代码都被包进了一个 <code>async move</code> 块。回忆一下,代码块本身就是表达式。整个块就是函数返回的那个表达式。</li>
<li>如上所述,这个异步代码块产生一个 <code>Option&lt;String&gt;</code> 类型的值。这个值与返回类型中的 <code>Output</code> 类型一致。这正类似于你已经见过的其它代码块。</li>
<li>这个新函数体之所以是 <code>async move</code> 块,是由它使用 <code>url</code> 参数的方式决定的。(本章后面会更详细地讨论 <code>async</code><code>async move</code> 的区别。)</li>
</ul>
<p>现在我们可以在 <code>main</code> 中调用 <code>page_title</code></p>
<h3 id="使用运行时执行异步函数"><a class="header" href="#使用运行时执行异步函数">使用运行时执行异步函数</a></h3>
<p>首先,我们只获取单个页面的标题,如示例 17-3 所示。不幸的是,这段代码还不能编译。</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<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>示例 17-3在 `main` 中通过一个用户提供的参数调用 `page_title` 函数</figcaption>
</figure>
<p>我们沿用了第十二章<a href="ch12-01-accepting-command-line-arguments.html">“接受命令行参数”</a>一节中获取命令行参数的模式。然后把 URL 参数传给 <code>page_title</code>,再等待它的结果。由于 future 产出的值是 <code>Option&lt;String&gt;</code>,我们使用 <code>match</code> 表达式来根据页面是否含有 <code>&lt;title&gt;</code> 打印不同的信息。</p>
<p>唯一能使用 <code>await</code> 关键字的地方,是 async 函数或 async 代码块中,而 Rust 又不允许我们把特殊的 <code>main</code> 函数标记为 <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><code>main</code> 不能标记为 <code>async</code> 的原因是异步代码需要一个 <em>运行时</em>:即一个管理执行异步代码细节的 Rust crate。一个程序的 <code>main</code> 函数可以 <em>初始化</em> 一个运行时,但是其 <em>自身</em> 并不是一个运行时。(稍后我们会进一步解释原因。)每一个执行异步代码的 Rust 程序必须至少有一个设置运行时并执行 futures 的地方。</p>
<p>大多数支持 async 的语言都会自带运行时,但 Rust 不会。相反Rust 有很多不同的异步运行时可供选择,每一种都针对自己的目标用例做了不同权衡。比如,一个拥有许多 CPU 核心和大量 RAM 的高吞吐 Web 服务器和一个单核、RAM 很小、甚至不能进行堆分配的微控制器,需求就截然不同。提供这些运行时的 crate 往往也会一并提供文件或网络 I/O 等常见功能的异步版本。</p>
<p>在这里,以及本章余下的部分,我们会使用 <code>trpl</code> crate 提供的 <code>block_on</code> 函数。它接受一个 future 作为参数,并阻塞当前线程,直到这个 future 运行完成为止。在内部,调用 <code>block_on</code> 会借助 <code>tokio</code> crate 设置一个运行时,用来执行传入的 future<code>trpl</code><code>block_on</code> 和其他运行时 crate 提供的同名函数行为类似)。一旦 future 完成,<code>block_on</code> 就会返回 future 产生的值。</p>
<p>我们当然可以把 <code>page_title</code> 返回的 future 直接传给 <code>block_on</code>,并在它完成后对得到的 <code>Option&lt;String&gt;</code> 进行匹配,就像我们在示例 17-3 中本来打算做的那样。不过,本章的大部分例子里(以及现实中的大多数 async 代码里),我们都不止会进行一次异步函数调用,因此我们改为传入一个 <code>async</code> 块,并在其中显式等待 <code>page_title</code> 的结果,如示例 17-4 所示。</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<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>示例 17-4使用 `trpl::block_on` 等待一个 async 代码块</figcaption>
</figure>
<p>当我们运行这段代码时,就会得到一开始期待的行为:</p>
<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>我们终于有了一些可以正常工作的异步代码!不过在我们添加代码让两个网址进行竞争之前,让我们简要地回顾一下 future 是如何工作的。</p>
<p>每一个 <em>await point</em>,也就是代码使用 <code>await</code> 关键字的地方,代表将控制权交还给运行时的地方。为此 Rust 需要记录异步代码块中涉及的状态,这样运行时可以去执行其他工作,并在准备好时回来继续推进当前的任务。这就像你通过编写一个枚举来保存每一个 <code>await</code> 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>编写代码来手动控制不同状态之间的转换是非常乏味且容易出错的特别是之后增加了更多功能和状态的时候。相反Rust 编译器自动创建并管理异步代码的状态机数据结构。如果你感兴趣的话:是的,正常的借用和所有权也全部适用于这些数据结构。幸运的是,编译器也会为我们处理这些检查,并提供友好的错误信息。本章稍后会讲解一些相关内容!</p>
<p>最终,总得有某个组件来执行这个状态机,而那个组件就是运行时。(这也是为什么在了解运行时时,你可能会看到 <em>executor</em> 这个词executor 是运行时中负责执行异步代码的那一部分。)</p>
<p>现在你就能理解,为什么编译器会在示例 17-3 中阻止我们把 <code>main</code> 本身写成异步函数了。如果 <code>main</code> 是 async 函数,那么就必须有别的东西来管理 <code>main</code> 返回的 future 对应的状态机;可 <code>main</code> 本身就是程序的入口点!因此,我们改为在 <code>main</code> 中调用 <code>trpl::block_on</code>,让它设置好运行时,并运行 <code>async</code> 块返回的 future直到执行完成。</p>
<blockquote>
<p>注意:有些运行时会提供宏,因此你<em>确实可以</em>写异步版的 <code>main</code> 函数。这些宏会把 <code>async fn main() { ... }</code> 重写成普通的 <code>fn main</code>,其逻辑和我们在示例 17-4 中手动做的事情一样:调用一个像 <code>trpl::block_on</code> 这样的函数,把 future 跑到完成为止。</p>
</blockquote>
<p>现在让我们把这些部分组合起来,看看如何编写并发代码。</p>
<h3 id="让两个-url-并发竞争"><a class="header" href="#让两个-url-并发竞争">让两个 URL 并发竞争</a></h3>
<p>在示例 17-5 中,我们会对从命令行传入的两个不同 URL 分别调用 <code>page_title</code>,并选出最先完成的那个 future。</p>
<figure class="listing">
<p><span class="file-name">文件名src/main.rs</span></p>
<!-- 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>示例 17-5对两个 URL 调用 `page_title`,看谁先返回</figcaption>
</figure>
<p>我们首先分别对用户提供的两个 URL 调用 <code>page_title</code>。随后把得到的 future 保存到 <code>title_fut_1</code><code>title_fut_2</code> 中。记住,它们此时还什么都没做,因为 future 是惰性的,而我们也还没有等待它们。接着我们把这些 future 传给 <code>trpl::select</code>,它会返回一个值,用来表明传入的 future 中哪一个最先完成。</p>
<blockquote>
<p>注意:在底层,<code>trpl::select</code> 建立在 <code>futures</code> crate 中更通用的 <code>select</code> 函数之上。<code>futures</code> crate 的 <code>select</code> 函数能做很多 <code>trpl::select</code> 做不到的事,不过它也带来了一些额外复杂性,所以我们暂时先跳过。</p>
</blockquote>
<p>任意一个 future 都有可能“获胜”,因此这里返回 <code>Result</code> 并不合理。相反,<code>trpl::select</code> 返回的是一个我们之前还没见过的类型:<code>trpl::Either</code><code>Either</code> 在某种程度上有点像 <code>Result</code>,也有两个分支;但不同的是,它并没有内建“成功”或“失败”的语义,而是用 <code>Left</code><code>Right</code> 来表示“这个或那个”。</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>如果第一个参数先完成,<code>select</code> 就返回 <code>Left</code>,其中包含该 future 的输出;如果第二个 future 先完成,则返回 <code>Right</code>,其中包含第二个 future 的输出。这正好对应函数调用时参数的顺序:第一个参数位于第二个参数的左边。</p>
<p>我们还更新了 <code>page_title</code>,让它把传入的 URL 一并返回。这样一来,即使最先返回的页面无法解析出 <code>&lt;title&gt;</code>,我们仍然可以打印出一条有意义的信息。有了这些数据之后,我们最后再调整 <code>println!</code> 的输出,让它既能显示哪个 URL 最先完成,也能在页面存在 <code>&lt;title&gt;</code> 时打印出标题内容。</p>
<p>至此,你已经构建出了一个可以工作的迷你网页抓取器!随便选两个 URL 运行一下这个命令行工具吧。你会发现有些站点总是比另一些更快,而另一些情况下则每次运行谁快谁慢都不一定。更重要的是,你已经掌握了使用 future 的基础知识,所以现在我们可以继续深入,看看 async 还能做些什么。</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch17-00-async-await.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg></span>
</a>
<a rel="next prefetch" href="ch17-02-concurrency-with-async.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg></span>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="ch17-00-async-await.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg></span>
</a>
<a rel="next prefetch" href="ch17-02-concurrency-with-async.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg></span>
</a>
</nav>
</div>
<template id=fa-eye><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM432 256c0 79.5-64.5 144-144 144s-144-64.5-144-144s64.5-144 144-144s144 64.5 144 144zM288 192c0 35.3-28.7 64-64 64c-11.5 0-22.3-3-31.6-8.4c-.2 2.8-.4 5.5-.4 8.4c0 53 43 96 96 96s96-43 96-96s-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6z"/></svg></span></template>
<template id=fa-eye-slash><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M38.8 5.1C28.4-3.1 13.3-1.2 5.1 9.2S-1.2 34.7 9.2 42.9l592 464c10.4 8.2 25.5 6.3 33.7-4.1s6.3-25.5-4.1-33.7L525.6 386.7c39.6-40.6 66.4-86.1 79.9-118.4c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C465.5 68.8 400.8 32 320 32c-68.2 0-125 26.3-169.3 60.8L38.8 5.1zM223.1 149.5C248.6 126.2 282.7 112 320 112c79.5 0 144 64.5 144 144c0 24.9-6.3 48.3-17.4 68.7L408 294.5c5.2-11.8 8-24.8 8-38.5c0-53-43-96-96-96c-2.8 0-5.6 .1-8.4 .4c5.3 9.3 8.4 20.1 8.4 31.6c0 10.2-2.4 19.8-6.6 28.3l-90.3-70.8zm223.1 298L373 389.9c-16.4 6.5-34.3 10.1-53 10.1c-79.5 0-144-64.5-144-144c0-6.9 .5-13.6 1.4-20.2L83.1 161.5C60.3 191.2 44 220.8 34.5 243.7c-3.3 7.9-3.3 16.7 0 24.6c14.9 35.7 46.2 87.7 93 131.1C174.5 443.2 239.2 480 320 480c47.8 0 89.9-12.9 126.2-32.5z"/></svg></span></template>
<template id=fa-copy><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M502.6 70.63l-61.25-61.25C435.4 3.371 427.2 0 418.7 0H255.1c-35.35 0-64 28.66-64 64l.0195 256C192 355.4 220.7 384 256 384h192c35.2 0 64-28.8 64-64V93.25C512 84.77 508.6 76.63 502.6 70.63zM464 320c0 8.836-7.164 16-16 16H255.1c-8.838 0-16-7.164-16-16L239.1 64.13c0-8.836 7.164-16 16-16h128L384 96c0 17.67 14.33 32 32 32h47.1V320zM272 448c0 8.836-7.164 16-16 16H63.1c-8.838 0-16-7.164-16-16L47.98 192.1c0-8.836 7.164-16 16-16H160V128H63.99c-35.35 0-64 28.65-64 64l.0098 256C.002 483.3 28.66 512 64 512h192c35.2 0 64-28.8 64-64v-32h-47.1L272 448z"/></svg></span></template>
<template id=fa-play><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg></span></template>
<template id=fa-clock-rotate-left><span class=fa-svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-30.8-30.8C155 85.5 203 64 256 64c106 0 192 86 192 192s-86 192-192 192c-40.8 0-78.6-12.7-109.7-34.4c-14.5-10.1-34.4-6.6-44.6 7.9s-6.6 34.4 7.9 44.6C151.2 495 201.7 512 256 512c141.4 0 256-114.6 256-256S397.4 0 256 0C185.3 0 121.3 28.7 75 75zm181 53c-13.3 0-24 10.7-24 24V256c0 6.4 2.5 12.5 7 17l72 72c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-65-65V152c0-13.3-10.7-24-24-24z"/></svg></span></template>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr-ef4e11c1.min.js"></script>
<script src="mark-09e88c2c.min.js"></script>
<script src="searcher-c2a407aa.js"></script>
<script src="clipboard-1626706a.min.js"></script>
<script src="highlight-abc7f01d.js"></script>
<script src="book-a0b12cfe.js"></script>
<!-- Custom JS scripts -->
<script src="ferris-2317480c.js"></script>
</div>
</body>
</html>