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/ch14-02-publishing-to-crate...

498 lines
46 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>将 crate 发布到 Crates.io - 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="将-crate-发布到-cratesio"><a class="header" href="#将-crate-发布到-cratesio">将 crate 发布到 Crates.io</a></h2>
<p><a href="https://github.com/rust-lang/book/blob/43b9ad334aaf7353e5708dba49f84f941b50ec4b/src/ch14-02-publishing-to-crates-io.md">ch14-02-publishing-to-crates-io.md</a></p>
<p>我们曾经在项目中使用 <a href="https://crates.io">crates.io</a><!-- ignore --> 上的包作为依赖,不过你也可以通过发布自己的包来向他人分享代码。<a href="https://crates.io">crates.io</a><!-- ignore --> 上的 crate 注册表会分发你包的源代码,因此它主要托管开源代码。</p>
<p>Rust 和 Cargo 提供了一些功能,让你发布的包更容易被他人找到和使用。接下来我们会介绍其中一些功能,然后说明如何发布包。</p>
<h3 id="编写有用的文档注释"><a class="header" href="#编写有用的文档注释">编写有用的文档注释</a></h3>
<p>准确的包文档有助于其他用户理解如何以及何时使用它们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用双斜杠 <code>//</code> 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为<strong>文档注释</strong><em>documentation comments</em>),它们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,它们意在让对库感兴趣的程序员理解如何<strong>使用</strong>这个 crate而不是它是如何被<strong>实现</strong>的。</p>
<p>文档注释使用三条斜杠 <code>///</code>,而不是两条斜杠,并且支持使用 Markdown 标记来格式化文本。将文档注释放在它所说明的项之前。示例 14-1 展示了名为 <code>my_crate</code> 的 crate 中一个 <code>add_one</code> 函数的文档注释。</p>
<p><span class="filename">文件名src/lib.rs</span></p>
<pre><code class="language-rust ignore">/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -&gt; i32 {
x + 1
}</code></pre>
<p><span class="caption">示例 14-1一个函数的文档注释</span></p>
<p>这里,我们描述了 <code>add_one</code> 函数的功能,接着以 <code>Examples</code> 为标题开始了一个小节,并给出了展示如何使用 <code>add_one</code> 函数的代码。可以运行 <code>cargo doc</code> 来根据这些文档注释生成 HTML 文档。这个命令会运行 Rust 自带的 <code>rustdoc</code> 工具,并将生成的 HTML 文档放到 <em>target/doc</em> 目录中。</p>
<p>为了方便起见,运行 <code>cargo doc --open</code> 会为当前 crate 的文档构建 HTML以及它所有依赖的文档并在浏览器中打开结果。定位到 <code>add_one</code> 函数时,你会看到文档注释中的文本是如何被渲染的,如图 14-1 所示:</p>
<img alt="Rendered HTML documentation for the `add_one` function of `my_crate`" src="img/trpl14-01.png" class="center" />
<p><span class="caption">图 14-1<code>add_one</code> 函数的文档注释 HTML</span></p>
<h4 id="常用章节"><a class="header" href="#常用章节">常用章节</a></h4>
<p>示例 14-1 中使用了 <code># Examples</code> Markdown 标题在 HTML 中创建了一个以 “Examples” 为标题的部分。其他一些 crate 作者经常在文档注释中使用的部分有:</p>
<ul>
<li><strong>Panics</strong>:函数在什么情况下可能会 <code>panic!</code>。不希望程序 panic 的调用者应确保不会在这些情况下调用该函数。</li>
<li><strong>Errors</strong>:如果函数返回 <code>Result</code>,说明可能出现哪些错误,以及什么条件会导致返回这些错误,会有助于调用者编写代码,以不同方式处理不同种类的错误。</li>
<li><strong>Safety</strong>:如果调用该函数是 <code>unsafe</code> 的(我们会在第二十章讨论不安全代码),这里应解释为什么它是不安全的,并说明函数要求调用者维持哪些不变式。</li>
</ul>
<p>大多数文档注释不需要包含所有这些章节,但这是一份很好的检查清单,可以提醒你关注用户会想了解的内容。</p>
<h4 id="文档注释作为测试"><a class="header" href="#文档注释作为测试">文档注释作为测试</a></h4>
<p>在文档注释中添加示例代码块,有助于展示如何使用你的库,而且还有一个额外的好处:运行 <code>cargo test</code> 时,文档中的示例代码也会作为测试运行!没有什么比带示例的文档更好了,但也没有什么比示例失效的文档更糟糕了。如果我们对示例 14-1 中 <code>add_one</code> 函数的文档运行 <code>cargo test</code>,会在测试结果中看到如下内容:</p>
<pre><code class="language-text"> Doc-tests my_crate
running 1 test
test src/lib.rs - add_one (line 5) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
</code></pre>
<p>现在,如果我们修改函数或示例中的任意一方,使示例里的 <code>assert_eq!</code> 触发 panic然后再次运行 <code>cargo test</code>,就会看到文档测试捕获到了示例与代码不同步的问题!</p>
<h4 id="注释包含项的结构"><a class="header" href="#注释包含项的结构">注释包含项的结构</a></h4>
<p><code>//!</code> 这种文档注释风格为“包含这些注释的项”添加文档,而不是为“位于这些注释之后的项”添加文档。我们通常在 crate 根文件(按惯例是 <em>src/lib.rs</em>)或模块内部使用这种文档注释,为整个 crate 或整个模块编写说明。</p>
<p>例如,为了添加描述包含 <code>add_one</code> 函数的 <code>my_crate</code> crate 的用途的文档,我们可以在 <em>src/lib.rs</em> 文件开头加入以 <code>//!</code> 开头的文档注释,如示例 14-2 所示:</p>
<p><span class="filename">文件名src/lib.rs</span></p>
<pre><code class="language-rust ignore">//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
// --snip--
<span class="boring">///
</span><span class="boring">/// # Examples
</span><span class="boring">///
</span><span class="boring">/// ```
</span><span class="boring">/// let arg = 5;
</span><span class="boring">/// let answer = my_crate::add_one(arg);
</span><span class="boring">///
</span><span class="boring">/// assert_eq!(6, answer);
</span><span class="boring">/// ```
</span><span class="boring">pub fn add_one(x: i32) -&gt; i32 {
</span><span class="boring"> x + 1
</span><span class="boring">}</span></code></pre>
<p><span class="caption">示例 14-2<code>my_crate</code> crate 整体的文档</span></p>
<p>注意,最后一行以 <code>//!</code> 开头的注释后面没有任何代码。因为我们使用的是 <code>//!</code> 而不是 <code>///</code>,所以这里记录的是“包含这条注释的项”的文档,而不是“紧随这条注释之后的项”的文档。在这里,这个项就是 <em>src/lib.rs</em> 文件,也就是 crate 根。这些注释描述的是整个 crate。</p>
<p>运行 <code>cargo doc --open</code> 后,这些注释会显示在 <code>my_crate</code> 文档首页的 crate 公有项列表上方,如图 14-2 所示:</p>
<img alt="Rendered HTML documentation with a comment for the crate as a whole" src="img/trpl14-02.png" class="center" />
<p><span class="caption">图 14-2包含 <code>my_crate</code> 整体描述的注释所渲染的文档</span></p>
<p>项内部的文档注释特别适合用来描述 crate 和模块。使用它们来解释这个容器整体的目的,可以帮助用户理解 crate 的组织方式。</p>
<h3 id="导出实用的公有-api"><a class="header" href="#导出实用的公有-api">导出实用的公有 API</a></h3>
<p>公有 API 的结构是你发布 crate 时主要需要考虑的。crate 用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。</p>
<p>第七章介绍了如何使用 <code>pub</code> 关键字使项公开,以及如何使用 <code>use</code> 关键字将项引入作用域。不过,在你开发 crate 时对你来说合理的结构,对用户而言可能并不方便。你可能想把结构体组织成一个包含多层的层级结构,但想使用你定义在深层级中的某个类型的人,可能很难发现它的存在。他们也可能会厌烦不得不写 <code>use my_crate::some_module::another_module::UsefulType;</code>,而不是更简单的 <code>use my_crate::UsefulType;</code></p>
<p>好消息是,如果这种结构对外部用户来说并不方便,你也不必重新安排内部组织。你可以使用 <code>pub use</code> 来重导出项,从而建立一个与私有结构不同的公有结构。<em>重导出re-export</em> 会把某个位置的公有项在另一个位置再次公开,就好像它原本就定义在那里一样。</p>
<p>例如,假设我们创建了一个名为 <code>art</code> 的库,用来建模艺术概念。在这个库里,有两个模块:<code>kinds</code> 模块包含两个枚举 <code>PrimaryColor</code><code>SecondaryColor</code><code>utils</code> 模块包含一个名为 <code>mix</code> 的函数,如示例 14-3 所示:</p>
<p><span class="filename">文件名src/lib.rs</span></p>
<pre><code class="language-rust noplayground test_harness">//! # Art
//!
//! A library for modeling artistic concepts.
pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}
pub mod utils {
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -&gt; SecondaryColor {
// --snip--
<span class="boring"> unimplemented!();
</span> }
}</code></pre>
<p><span class="caption">示例 14-3一个库 <code>art</code> 其组织包含 <code>kinds</code><code>utils</code> 模块</span></p>
<p>图 14-3 展示了 <code>cargo doc</code> 为这个 crate 生成的文档首页。</p>
<img alt="Rendered documentation for the `art` crate that lists the `kinds` and `utils` modules" src="img/trpl14-03.png" class="center" />
<p><span class="caption">图 14-3包含 <code>kinds</code><code>utils</code> 模块的库 <code>art</code> 的文档首页</span></p>
<p>注意 <code>PrimaryColor</code><code>SecondaryColor</code> 类型、以及 <code>mix</code> 函数都没有在首页中列出。我们必须点击 <code>kinds</code><code>utils</code> 才能看到它们。</p>
<p>依赖这个库的另一个 crate 需要使用 <code>use</code> 语句,把 <code>art</code> 中的项引入作用域,同时必须指定当前定义的模块结构。示例 14-4 展示了一个使用 <code>art</code> crate 中 <code>PrimaryColor</code><code>mix</code> 的 crate</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust ignore">use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}</code></pre>
<p><span class="caption">示例 14-4一个通过导出内部结构使用 <code>art</code> crate 中项的 crate</span></p>
<p>示例 14-4 中这段代码的作者,必须先弄清楚 <code>PrimaryColor</code><code>kinds</code> 模块中,而 <code>mix</code><code>utils</code> 模块中。<code>art</code> crate 的模块结构,对开发 <code>art</code> crate 的人来说比对使用它的人更有意义。这种内部结构并没有给想理解如何使用 <code>art</code> crate 的人提供有价值的信息,反而会带来困惑,因为用户必须先搞清楚该去哪里找需要的内容,还要在 <code>use</code> 语句中写出模块名。</p>
<p>为了从公有 API 中去掉内部组织细节,我们可以修改示例 14-3 中的 <code>art</code> crate加入 <code>pub use</code> 语句,在顶层重导出这些项,如示例 14-5 所示:</p>
<p><span class="filename">文件名src/lib.rs</span></p>
<pre><code class="language-rust ignore">//! # Art
//!
//! A library for modeling artistic concepts.
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
pub mod kinds {
// --snip--
<span class="boring"> /// The primary colors according to the RYB color model.
</span><span class="boring"> pub enum PrimaryColor {
</span><span class="boring"> Red,
</span><span class="boring"> Yellow,
</span><span class="boring"> Blue,
</span><span class="boring"> }
</span><span class="boring">
</span><span class="boring"> /// The secondary colors according to the RYB color model.
</span><span class="boring"> pub enum SecondaryColor {
</span><span class="boring"> Orange,
</span><span class="boring"> Green,
</span><span class="boring"> Purple,
</span><span class="boring"> }
</span>}
pub mod utils {
// --snip--
<span class="boring"> use crate::kinds::*;
</span><span class="boring">
</span><span class="boring"> /// Combines two primary colors in equal amounts to create
</span><span class="boring"> /// a secondary color.
</span><span class="boring"> pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -&gt; SecondaryColor {
</span><span class="boring"> SecondaryColor::Orange
</span><span class="boring"> }
</span>}</code></pre>
<p><span class="caption">示例 14-5增加 <code>pub use</code> 语句重导出项</span></p>
<p>现在,<code>cargo doc</code> 为这个 crate 生成的 API 文档会在首页列出这些重导出项及其链接,如图 14-4 所示,这使 <code>PrimaryColor</code><code>SecondaryColor</code><code>mix</code> 更容易被找到。</p>
<img alt="Rendered documentation for the `art` crate with the re-exports on the front page" src="img/trpl14-04.png" class="center" />
<p><span class="caption">图 14-4列出重导出项的 <code>art</code> 文档首页</span></p>
<p><code>art</code> crate 的用户仍然可以像示例 14-4 那样看到并使用示例 14-3 中的内部结构,也可以使用示例 14-5 中更方便的结构,如示例 14-6 所示:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust ignore">use art::PrimaryColor;
use art::mix;
fn main() {
// --snip--
<span class="boring"> let red = PrimaryColor::Red;
</span><span class="boring"> let yellow = PrimaryColor::Yellow;
</span><span class="boring"> mix(red, yellow);
</span>}</code></pre>
<p><span class="caption">示例 14-6一个使用 <code>art</code> crate 中重导出项的程序</span></p>
<p>在存在很多嵌套模块的情况下,使用 <code>pub use</code> 将类型重导出到顶层,会显著改善使用这个 crate 的体验。<code>pub use</code> 的另一个常见用法,是把当前 crate 的某个依赖中的定义重新导出,让那个 crate 的定义成为你这个 crate 公有 API 的一部分。</p>
<p>创建有用的公有 API 结构更像是一门艺术,而不是科学;你可以不断迭代,找到最适合用户的 API。选择 <code>pub use</code> 能让你在 crate 内部结构的组织方式上保持灵活,并将其与你呈现给用户的结构解耦。可以看看你安装过的一些 crate 的源码,观察它们的内部结构是否和公有 API 不同。</p>
<h3 id="创建-cratesio-账号"><a class="header" href="#创建-cratesio-账号">创建 Crates.io 账号</a></h3>
<p>在发布任何 crate 之前,你需要在 <a href="https://crates.io">crates.io</a><!-- ignore --> 上创建账号并获取一个 API token。为此请访问 <a href="https://crates.io">crates.io</a><!-- ignore --> 首页,并通过 GitHub 账号登录。(目前 GitHub 账号仍然是必需的,不过未来这个网站可能会支持其他注册方式。)登录之后,前往 <a href="https://crates.io/me/">https://crates.io/me/</a><!-- ignore --> 的账户设置页面获取 API key。然后运行 <code>cargo login</code> 命令,并在提示时粘贴你的 API key如下所示</p>
<pre><code class="language-console">$ cargo login
abcdefghijklmnopqrstuvwxyz012345
</code></pre>
<p>这个命令会把你的 API token 告诉 Cargo并将其保存在本地的 <em>~/.cargo/credentials</em> 文件中。注意,这个 token 是一个<strong>秘密</strong>,不应该与任何人共享。如果你因为任何原因泄露了它,应立即到 <a href="https://crates.io">crates.io</a><!-- ignore --> 撤销并重新生成一个 token。</p>
<h3 id="向新-crate-添加元数据"><a class="header" href="#向新-crate-添加元数据">向新 crate 添加元数据</a></h3>
<p>比如说你已经有一个希望发布的 crate。在发布之前你需要在 crate 的 <em>Cargo.toml</em> 文件的 <code>[package]</code> 部分增加一些本 crate 的元数据metadata</p>
<p>首先crate 需要一个唯一的名称。虽然在本地开发 crate 时,你可以随意命名,但 <a href="https://crates.io">crates.io</a><!-- ignore --> 上的 crate 名称遵循先到先得的原则。一旦某个 crate 名称已经被占用,就没有其他人能再用这个名称发布 crate。请搜索你想使用的名称确认它是否已被占用。如果没有就把 <em>Cargo.toml</em><code>[package]</code> 里的 <code>name</code> 字段改成你想发布时使用的名称,如下所示:</p>
<p><span class="filename">文件名Cargo.toml</span></p>
<pre><code class="language-toml">[package]
name = "guessing_game"
</code></pre>
<p>即使你选择了一个唯一的名称,如果此时尝试运行 <code>cargo publish</code> 发布该 crate 的话,会得到一个警告接着是一个错误:</p>
<pre><code class="language-console">$ cargo publish
Updating crates.io index
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
--snip--
error: failed to publish to registry at https://crates.io
Caused by:
the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields
</code></pre>
<p>这个错误是因为我们缺少一些关键信息:关于该 crate 用途的描述,以及用户可以在什么许可条款下使用它。在 <em>Cargo.toml</em> 中添加一两句简短描述即可,因为它会在搜索结果中和你的 crate 一起显示。对于 <code>license</code> 字段,你需要填写一个<strong>许可证标识符值</strong><em>license identifier value</em>)。<a href="https://spdx.org/licenses/">Linux 基金会的 Software Package Data Exchange (SPDX)</a> 列出了可用的标识符。例如,如果要指定 crate 使用 MIT License就添加 <code>MIT</code> 标识符:</p>
<p><span class="filename">文件名Cargo.toml</span></p>
<pre><code class="language-toml">[package]
name = "guessing_game"
license = "MIT"
</code></pre>
<p>如果你想使用 SPDX 中不存在的许可证,就需要把许可证文本放入一个文件中,将该文件包含到项目里,然后使用 <code>license-file</code> 指定该文件名,而不是使用 <code>license</code> 字段。</p>
<p>关于项目应采用何种许可证的建议超出了本书的范围。很多 Rust 社区成员选择与 Rust 本身相同的许可证,也就是双许可证 <code>MIT OR Apache-2.0</code>。这个例子也说明了,你可以用 <code>OR</code> 分隔多个许可证标识符,来为项目指定多个许可证。</p>
<p>那么,有了唯一的名称、版本号、由 <code>cargo new</code> 新建项目时增加的作者信息、描述和所选择的 license已经准备好发布的项目的 <em>Cargo.toml</em> 文件可能看起来像这样:</p>
<p><span class="filename">文件名Cargo.toml</span></p>
<pre><code class="language-toml">[package]
name = "guessing_game"
version = "0.1.0"
edition = "2024"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"
[dependencies]
</code></pre>
<p><a href="https://doc.rust-lang.org/cargo/">Cargo 文档</a> 还描述了其他可指定的元数据,它们可以帮助你的 crate 更容易被发现和使用!</p>
<h3 id="发布到-cratesio"><a class="header" href="#发布到-cratesio">发布到 Crates.io</a></h3>
<p>现在,我们已经创建了账号、保存了 API token、为 crate 选好了名字,并填入了所需的元数据,你就可以发布了!发布 crate 会将该 crate 的某个特定版本上传到 <a href="https://crates.io">crates.io</a><!-- ignore --> 供他人使用。</p>
<p>发布 crate 时务必小心,因为发布是<strong>永久性的</strong>。对应版本无法被覆盖,其代码也无法被删除。<a href="https://crates.io">crates.io</a><!-- ignore --> 的一个主要目标,是充当代码的永久归档服务器,这样所有依赖 <a href="https://crates.io">crates.io</a><!-- ignore --> 上 crate 的项目都能一直正常工作。而如果允许删除版本,就无法实现这一目标。不过,可发布的版本号数量并没有限制。</p>
<p>再次运行 <code>cargo publish</code> 命令。这次它应该会成功:</p>
<pre><code class="language-console">$ cargo publish
Updating crates.io index
Packaging guessing_game v0.1.0 (file:///projects/guessing_game)
Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
</code></pre>
<p>至此,你已经把代码分享给 Rust 社区了,任何人都可以轻松地把你的 crate 加入自己的项目依赖中。</p>
<h3 id="发布现有-crate-的新版本"><a class="header" href="#发布现有-crate-的新版本">发布现有 crate 的新版本</a></h3>
<p>当你修改了 crate 并准备发布新版本时,修改 <em>Cargo.toml</em><code>version</code> 的值。请使用<a href="https://semver.org/">语义化版本控制规则</a>,根据修改的类型决定下一个版本号。然后再次运行 <code>cargo publish</code> 来上传新版本。</p>
<h3 id="使用-cargo-yank-从-cratesio-撤回版本"><a class="header" href="#使用-cargo-yank-从-cratesio-撤回版本">使用 <code>cargo yank</code> 从 Crates.io 撤回版本</a></h3>
<p>虽然你不能删除 crate 的历史版本但可以阻止未来的新项目把它加入依赖。这在某个版本因为某种原因损坏时会很有用。为此Cargo 支持对某个版本执行<strong>撤回</strong><em>yank</em>)。</p>
<p><strong>撤回</strong>某个版本会阻止新项目依赖这个版本,不过所有已经依赖它的项目仍然可以下载并继续依赖它。从本质上说,撤回意味着:所有已有 <em>Cargo.lock</em> 的项目都不会因此损坏,而任何新生成的 <em>Cargo.lock</em> 都不会再使用被撤回的版本。</p>
<p>要撤回 crate 的某个版本,请在之前发布该 crate 的目录中运行 <code>cargo yank</code>,并指定要撤回的版本。例如,如果我们发布了名为 <code>guessing_game</code> 的 crate 的 <code>1.0.1</code> 版本,并想撤回它,就在 <code>guessing_game</code> 项目目录中运行:</p>
<pre><code class="language-console">$ cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game@1.0.1
</code></pre>
<p>你也可以撤销这次撤回,让项目重新可以依赖该版本,只需在命令中加上 <code>--undo</code></p>
<pre><code class="language-console">$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game@1.0.1
</code></pre>
<p>撤回<strong>不会</strong>删除任何代码。例如,撤回功能并不能删除你不小心上传的秘密信息。如果发生了这种情况,请立刻轮换这些秘密信息。</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch14-01-release-profiles.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="ch14-03-cargo-workspaces.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="ch14-01-release-profiles.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="ch14-03-cargo-workspaces.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>