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...

494 lines
51 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="zh-CN" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>将 crate 发布到 Crates.io - Rust 程序设计语言 简体中文版</title>
<!-- Custom HTML head -->
<meta name="description" content="Rust 程序设计语言 简体中文版">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="ferris.css">
<link rel="stylesheet" href="theme/2018-edition.css">
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var 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>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("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 = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><a href="title-page.html">Rust 程序设计语言</a></li><li class="chapter-item expanded affix "><a href="foreword.html">前言</a></li><li class="chapter-item expanded affix "><a href="ch00-00-introduction.html">简介</a></li><li class="chapter-item expanded "><a href="ch01-00-getting-started.html"><strong aria-hidden="true">1.</strong> 入门指南</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch01-01-installation.html"><strong aria-hidden="true">1.1.</strong> 安装</a></li><li class="chapter-item expanded "><a href="ch01-02-hello-world.html"><strong aria-hidden="true">1.2.</strong> Hello, World!</a></li><li class="chapter-item expanded "><a href="ch01-03-hello-cargo.html"><strong aria-hidden="true">1.3.</strong> Hello, Cargo!</a></li></ol></li><li class="chapter-item expanded "><a href="ch02-00-guessing-game-tutorial.html"><strong aria-hidden="true">2.</strong> 写个猜数字游戏</a></li><li class="chapter-item expanded "><a href="ch03-00-common-programming-concepts.html"><strong aria-hidden="true">3.</strong> 常见编程概念</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch03-01-variables-and-mutability.html"><strong aria-hidden="true">3.1.</strong> 变量与可变性</a></li><li class="chapter-item expanded "><a href="ch03-02-data-types.html"><strong aria-hidden="true">3.2.</strong> 数据类型</a></li><li class="chapter-item expanded "><a href="ch03-03-how-functions-work.html"><strong aria-hidden="true">3.3.</strong> 函数</a></li><li class="chapter-item expanded "><a href="ch03-04-comments.html"><strong aria-hidden="true">3.4.</strong> 注释</a></li><li class="chapter-item expanded "><a href="ch03-05-control-flow.html"><strong aria-hidden="true">3.5.</strong> 控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-understanding-ownership.html"><strong aria-hidden="true">4.</strong> 认识所有权</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch04-01-what-is-ownership.html"><strong aria-hidden="true">4.1.</strong> 什么是所有权?</a></li><li class="chapter-item expanded "><a href="ch04-02-references-and-borrowing.html"><strong aria-hidden="true">4.2.</strong> 引用与借用</a></li><li class="chapter-item expanded "><a href="ch04-03-slices.html"><strong aria-hidden="true">4.3.</strong> Slice 类型</a></li></ol></li><li class="chapter-item expanded "><a href="ch05-00-structs.html"><strong aria-hidden="true">5.</strong> 使用结构体组织相关联的数据</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch05-01-defining-structs.html"><strong aria-hidden="true">5.1.</strong> 结构体的定义和实例化</a></li><li class="chapter-item expanded "><a href="ch05-02-example-structs.html"><strong aria-hidden="true">5.2.</strong> 结构体示例程序</a></li><li class="chapter-item expanded "><a href="ch05-03-method-syntax.html"><strong aria-hidden="true">5.3.</strong> 方法语法</a></li></ol></li><li class="chapter-item expanded "><a href="ch06-00-enums.html"><strong aria-hidden="true">6.</strong> 枚举和模式匹配</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch06-01-defining-an-enum.html"><strong aria-hidden="true">6.1.</strong> 枚举的定义</a></li><li class="chapter-item expanded "><a href="ch06-02-match.html"><strong aria-hidden="true">6.2.</strong> match 控制流结构</a></li><li class="chapter-item expanded "><a href="ch06-03-if-let.html"><strong aria-hidden="true">6.3.</strong> if let 简洁控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch07-00-managing-growing-projects-with-packages-crates-and-modules.html"><strong aria-hidden="true">7.</strong> 使用包、Crate 和模块管理不断增长的项目</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch07-01-packages-and-crates.html"><strong aria-hidden="true">7.1.</strong> 包和 Crate</a></li><li class="chapter-item expanded "><a href="ch07-02-defining-modules-to-control-scope-and-privacy.html"><strong aria-hidden="true">7.2.</strong> 定义模块来控制作用域与私有性</a></li><li class="chapter-item expanded "><a href="ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html"><strong aria-hidden="true">7.3.</strong> 引用模块项目的路径</a></li><li class="chapter-item expanded "><a href="ch07-04-bringing-paths-into-scope-with-the-use-keyword.html"><strong aria-hidden="true">7.4.</strong> 使用 use 关键字将路径引入作用域</a></li><li class="chapter-item expanded "><a href="ch07-05-separating-modules-into-different-files.html"><strong aria-hidden="true">7.5.</strong> 将模块拆分成多个文件</a></li></ol></li><li class="chapter-item expanded "><a href="ch08-00-common-collections.html"><strong aria-hidden="true">8.</strong> 常见集合</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch08-01-vectors.html"><strong aria-hidden="true">8.1.</strong> 使用 Vector 储存列表</a></li><li class="chapter-item expanded "><a href="ch08-02-strings.html"><strong aria-hidden="true">8.2.</strong> 使用字符串储存 UTF-8 编码的文本</a></li><li class="chapter-item expanded "><a href="ch08-03-hash-maps.html"><strong aria-hidden="true">8.3.</strong> 使用 Hash Map 储存键值对</a></li></ol></li><li class="chapter-item expanded "><a href="ch09-00-error-handling.html"><strong aria-hidden="true">9.</strong> 错误处理</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch09-01-unrecoverable-errors-with-panic.html"><strong aria-hidden="true">9.1.</strong> 用 panic! 处理不可恢复的错误</a></li><li class="chapter-item expanded "><a href="ch09-02-recoverable-errors-with-result.html"><strong aria-hidden="true">9.2.</strong> 用 Result 处理可恢复的错误</a></li><li class="chapter-item expanded "><a href="ch09-03-to-panic-or-not-to-panic.html"><strong aria-hidden="true">9.3.</strong> 要不要 panic!</a></li></ol></li><li class="chapter-item expanded "><a href="ch10-00-generics.html"><strong aria-hidden="true">10.</strong> 泛型、Trait 和生命周期</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch10-01-syntax.html"><strong aria-hidden="true">10.1.</strong> 泛型数据类型</a></li><li class="chapter-item expanded "><a href="ch10-02-traits.html"><strong aria-hidden="true">10.2.</strong> Trait定义共同行为</a></li><li class="chapter-item expanded "><a href="ch10-03-lifetime-syntax.html"><strong aria-hidden="true">10.3.</strong> 生命周期确保引用有效</a></li></ol></li><li class="chapter-item expanded "><a href="ch11-00-testing.html"><strong aria-hidden="true">11.</strong> 编写自动化测试</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch11-01-writing-tests.html"><strong aria-hidden="true">11.1.</strong> 如何编写测试</a></li><li class="chapter-item expanded "><a href="ch11-02-running-tests.html"><strong aria-hidden="true">11.2.</strong> 控制测试如何运行</a></li><li class="chapter-item expanded "><a href="ch11-03-test-organization.html"><strong aria-hidden="true">11.3.</strong> 测试的组织结构</a></li></ol></li><li class="chapter-item expanded "><a href="ch12-00-an-io-project.html"><strong aria-hidden="true">12.</strong> 一个 I/O 项目:构建命令行程序</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch12-01-accepting-command-line-arguments.html"><strong aria-hidden="true">12.1.</strong> 接受命令行参数</a></li><li class="chapter-item expanded "><a href="ch12-02-reading-a-file.html"><strong aria-hidden="true">12.2.</strong> 读取文件</a></li><li class="chapter-item expanded "><a href="ch12-03-improving-error-handling-and-modularity.html"><strong aria-hidden="true">12.3.</strong> 重构以改进模块化与错误处理</a></li><li class="chapter-item expanded "><a href="ch12-04-testing-the-librarys-functionality.html"><strong aria-hidden="true">12.4.</strong> 采用测试驱动开发完善库的功能</a></li><li class="chapter-item expanded "><a href="ch12-05-working-with-environment-variables.html"><strong aria-hidden="true">12.5.</strong> 处理环境变量</a></li><li class="chapter-item expanded "><a href="ch12-06-writing-to-stderr-instead-of-stdout.html"><strong aria-hidden="true">12.6.</strong> 将错误信息输出到标准错误而不是标准输出</a></li></ol></li><li class="chapter-item expanded "><a href="ch13-00-functional-features.html"><strong aria-hidden="true">13.</strong> Rust 中的函数式语言功能:迭代器与闭包</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch13-01-closures.html"><strong aria-hidden="true">13.1.</strong> 闭包:可以捕获其环境的匿名函数</a></li><li class="chapter-item expanded "><a href="ch13-02-iterators.html"><strong aria-hidden="true">13.2.</strong> 使用迭代器处理元素序列</a></li><li class="chapter-item expanded "><a href="ch13-03-improving-our-io-project.html"><strong aria-hidden="true">13.3.</strong> 改进之前的 I/O 项目</a></li><li class="chapter-item expanded "><a href="ch13-04-performance.html"><strong aria-hidden="true">13.4.</strong> 性能比较:循环对迭代器</a></li></ol></li><li class="chapter-item expanded "><a href="ch14-00-more-about-cargo.html"><strong aria-hidden="true">14.</strong> 更多关于 Cargo 和 Crates.io 的内容</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch14-01-release-profiles.html"><strong aria-hidden="true">14.1.</strong> 采用发布配置自定义构建</a></li><li class="chapter-item expanded "><a href="ch14-02-publishing-to-crates-io.html" class="active"><strong aria-hidden="true">14.2.</strong> 将 crate 发布到 Crates.io</a></li><li class="chapter-item expanded "><a href="ch14-03-cargo-workspaces.html"><strong aria-hidden="true">14.3.</strong> Cargo 工作空间</a></li><li class="chapter-item expanded "><a href="ch14-04-installing-binaries.html"><strong aria-hidden="true">14.4.</strong> 使用 cargo install 安装二进制文件</a></li><li class="chapter-item expanded "><a href="ch14-05-extending-cargo.html"><strong aria-hidden="true">14.5.</strong> Cargo 自定义扩展命令</a></li></ol></li><li class="chapter-item expanded "><a href="ch15-00-smart-pointers.html"><strong aria-hidden="true">15.</strong> 智能指针</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch15-01-box.html"><strong aria-hidden="true">15.1.</strong> 使用 Box&lt;T&gt; 指向堆上数据</a></li><li class="chapter-item expanded "><a href="ch15-02-deref.html"><strong aria-hidden="true">15.2.</strong> 使用 Deref Trait 将智能指针当作常规引用处理</a></li><li class="chapter-item expanded "><a href="ch15-03-drop.html"><strong aria-hidden="true">15.3.</strong> 使用 Drop Trait 运行清理代码</a></li><li class="chapter-item expanded "><a href="ch15-04-rc.html"><strong aria-hidden="true">15.4.</strong> Rc&lt;T&gt; 引用计数智能指针</a></li><li class="chapter-item expanded "><a href="ch15-05-interior-mutability.html"><strong aria-hidden="true">15.5.</strong> RefCell&lt;T&gt; 与内部可变性模式</a></li><li class="chapter-item expanded "><a href="ch15-06-reference-cycles.html"><strong aria-hidden="true">15.6.</strong> 引用循环会导致内存泄漏</a></li></ol></li><li class="chapter-item expanded "><a href="ch16-00-concurrency.html"><strong aria-hidden="true">16.</strong> 无畏并发</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch16-01-threads.html"><strong aria-hidden="true">16.1.</strong> 使用线程同时地运行代码</a></li><li class="chapter-item expanded "><a href="ch16-02-message-passing.html"><strong aria-hidden="true">16.2.</strong> 使用消息传递在线程间通信</a></li><li class="chapter-item expanded "><a href="ch16-03-shared-state.html"><strong aria-hidden="true">16.3.</strong> 共享状态并发</a></li><li class="chapter-item expanded "><a href="ch16-04-extensible-concurrency-sync-and-send.html"><strong aria-hidden="true">16.4.</strong> 使用 Sync 与 Send Traits 的可扩展并发</a></li></ol></li><li class="chapter-item expanded "><a href="ch17-00-oop.html"><strong aria-hidden="true">17.</strong> Rust 的面向对象编程特性</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch17-01-what-is-oo.html"><strong aria-hidden="true">17.1.</strong> 面向对象语言的特点</a></li><li class="chapter-item expanded "><a href="ch17-02-trait-objects.html"><strong aria-hidden="true">17.2.</strong> 顾及不同类型值的 trait 对象</a></li><li class="chapter-item expanded "><a href="ch17-03-oo-design-patterns.html"><strong aria-hidden="true">17.3.</strong> 面向对象设计模式的实现</a></li></ol></li><li class="chapter-item expanded "><a href="ch18-00-patterns.html"><strong aria-hidden="true">18.</strong> 模式与模式匹配</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch18-01-all-the-places-for-patterns.html"><strong aria-hidden="true">18.1.</strong> 所有可能会用到模式的位置</a></li><li class="chapter-item expanded "><a href="ch18-02-refutability.html"><strong aria-hidden="true">18.2.</strong> Refutability可反驳性: 模式是否会匹配失效</a></li><li class="chapter-item expanded "><a href="ch18-03-pattern-syntax.html"><strong aria-hidden="true">18.3.</strong> 模式语法</a></li></ol></li><li class="chapter-item expanded "><a href="ch19-00-advanced-features.html"><strong aria-hidden="true">19.</strong> 高级特征</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch19-01-unsafe-rust.html"><strong aria-hidden="true">19.1.</strong> 不安全的 Rust</a></li><li class="chapter-item expanded "><a href="ch19-03-advanced-traits.html"><strong aria-hidden="true">19.2.</strong> 高级 trait</a></li><li class="chapter-item expanded "><a href="ch19-04-advanced-types.html"><strong aria-hidden="true">19.3.</strong> 高级类型</a></li><li class="chapter-item expanded "><a href="ch19-05-advanced-functions-and-closures.html"><strong aria-hidden="true">19.4.</strong> 高级函数与闭包</a></li><li class="chapter-item expanded "><a href="ch19-06-macros.html"><strong aria-hidden="true">19.5.</strong></a></li></ol></li><li class="chapter-item expanded "><a href="ch20-00-final-project-a-web-server.html"><strong aria-hidden="true">20.</strong> 最后的项目:构建多线程 web server</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch20-01-single-threaded.html"><strong aria-hidden="true">20.1.</strong> 建立单线程 web server</a></li><li class="chapter-item expanded "><a href="ch20-02-multithreaded.html"><strong aria-hidden="true">20.2.</strong> 将单线程 server 变为多线程 server</a></li><li class="chapter-item expanded "><a href="ch20-03-graceful-shutdown-and-cleanup.html"><strong aria-hidden="true">20.3.</strong> 优雅停机与清理</a></li></ol></li><li class="chapter-item expanded "><a href="appendix-00.html"><strong aria-hidden="true">21.</strong> 附录</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="appendix-01-keywords.html"><strong aria-hidden="true">21.1.</strong> A - 关键字</a></li><li class="chapter-item expanded "><a href="appendix-02-operators.html"><strong aria-hidden="true">21.2.</strong> B - 运算符与符号</a></li><li class="chapter-item expanded "><a href="appendix-03-derivable-traits.html"><strong aria-hidden="true">21.3.</strong> C - 可派生的 trait</a></li><li class="chapter-item expanded "><a href="appendix-04-useful-development-tools.html"><strong aria-hidden="true">21.4.</strong> D - 实用开发工具</a></li><li class="chapter-item expanded "><a href="appendix-05-editions.html"><strong aria-hidden="true">21.5.</strong> E - 版本</a></li><li class="chapter-item expanded "><a href="appendix-06-translation.html"><strong aria-hidden="true">21.6.</strong> F - 本书译本</a></li><li class="chapter-item expanded "><a href="appendix-07-nightly-rust.html"><strong aria-hidden="true">21.7.</strong> G - Rust 是如何开发的与 “Nightly Rust”</a></li></ol></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</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">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/KaiserY/trpl-zh-cn/tree/main" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/KaiserY/trpl-zh-cn/edit/main/src/ch14-02-publishing-to-crates-io.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h2 id="将-crate-发布到-cratesio"><a class="header" href="#将-crate-发布到-cratesio">将 crate 发布到 Crates.io</a></h2>
<blockquote>
<p><a href="https://github.com/rust-lang/book/blob/main/src/ch14-02-publishing-to-crates-io.md">ch14-02-publishing-to-crates-io.md</a> <br>
commit 3f2a6ef48943ade3e9c0eb23d69e2b8b41f057f1</p>
</blockquote>
<p>我们曾经在项目中使用 <a href="https://crates.io">crates.io</a><!-- ignore --> 上的包作为依赖,不过你也可以通过发布自己的包来向他人分享代码。<a href="https://crates.io">crates.io</a><!-- ignore --> 用来分发包的源代码,所以它主要托管开源代码。</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 文档(同时还有所有 crate 依赖的文档)的 HTML 并在浏览器中打开。导航到 <code>add_one</code> 函数将会发现文档注释的文本是如何渲染的,如图 14-1 所示:</p>
<img alt="`my_crate` 的 `add_one` 函数所渲染的文档注释 HTML" 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> 的场景。并不希望程序崩溃的函数调用者应该确保他们不会在这些情况下调用此函数。</li>
<li><strong>Errors</strong>:如果这个函数返回 <code>Result</code>,此部分描述可能会出现何种错误以及什么情况会造成这些错误,这有助于调用者编写代码来采用不同的方式处理不同的错误。</li>
<li><strong>Safety</strong>:如果这个函数使用 <code>unsafe</code> 代码(这会在第十九章讨论),这一部分应该会涉及到期望函数调用者支持的确保 <code>unsafe</code> 块中代码正常工作的不变条件invariants</li>
</ul>
<p>大部分文档注释不需要所有这些部分,不过这是一个提醒你检查调用你代码的用户有兴趣了解的内容的列表。</p>
<h4 id="文档注释作为测试"><a class="header" href="#文档注释作为测试">文档注释作为测试</a></h4>
<p>在文档注释中增加示例代码块是一个清楚的表明如何使用库的方法,这么做还有一个额外的好处:<code>cargo test</code> 也会像测试那样运行文档中的示例代码!没有什么比有例子的文档更好的了,但最糟糕的莫过于写完文档后改动了代码,而导致例子不能正常工作。尝试 <code>cargo test</code> 运行像示例 14-1 中 <code>add_one</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="crate 整体注释所渲染的 HTML 文档" 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="使用-pub-use-导出合适的公有-api"><a class="header" href="#使用-pub-use-导出合适的公有-api">使用 <code>pub use</code> 导出合适的公有 API</a></h3>
<p>公有 API 的结构是你发布 crate 时主要需要考虑的。crate 用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。</p>
<p>第七章介绍了如何使用 <code>mod</code> 关键字来将代码组织进模块中,如何使用 <code>pub</code> 关键字将项变为公有,和如何使用 <code>use</code> 关键字将项引入作用域。然而你开发时候使用的文件架构可能并不方便用户。你的结构可能是一个包含多个层级的分层结构,不过这对于用户来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦要使用 <code>use my_crate::some_module::another_module::UsefulType;</code> 而不是 <code>use my_crate::UsefulType;</code> 来使用类型。</p>
<p>好消息是,即使文件结构对于用户来说 <strong>不是</strong> 很方便,你也无需重新安排内部组织:你可以选择使用 <code>pub use</code> 重导出re-export项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置好像它就定义在这个新位置一样。</p>
<p>例如,假设我们创建了一个描述美术信息的库 <code>art</code>。这个库中包含了一个有两个枚举 <code>PrimaryColor</code><code>SecondaryColor</code> 的模块 <code>kinds</code>,以及一个包含函数 <code>mix</code> 的模块 <code>utils</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><code>cargo doc</code> 所生成的 crate 文档首页如图 14-3 所示:</p>
<img alt="包含 `kinds` 和 `utils` 模块的 `art`" 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>art</code> crate 代码的作者不得不搞清楚 <code>PrimaryColor</code> 位于 <code>kinds</code> 模块而 <code>mix</code> 位于 <code>utils</code> 模块。<code>art</code> crate 的模块结构相比使用它的开发者来说对编写它的开发者更有意义。其内部结构并没有对尝试理解如何使用 <code>art</code> crate 的人提供任何有价值的信息,相反因为不得不搞清楚所需的内容在何处和必须在 <code>use</code> 语句中指定模块名称而显得混乱。</p>
<p>为了从公有 API 中去掉 crate 的内部组织,我们可以采用示例 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>现在此 crate 由 <code>cargo doc</code> 生成的 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-10<code>art</code> 文档的首页,这里列出了重导出的项</span></p>
<p><code>art</code> crate 的用户仍然可以看见和选择使用示例 14-4 中的内部结构,或者可以使用示例 14-5 中更为方便的结构,如示例 14-6 所示:</p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><code class="language-rust ignore">use art::mix;
use art::PrimaryColor;
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 token。接着使用该 API token 运行 <code>cargo login</code> 命令,像这样:</p>
<pre><code class="language-console">$ cargo login abcdefghijklmnopqrstuvwxyz012345
</code></pre>
<p>这个命令会通知 Cargo 你的 API token 并将其储存在本地的 <em>~/.cargo/credentials</em> 文件中。注意这个 token 是一个 <strong>秘密</strong><strong>secret</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> 里的名称为你希望用于发布的名称,像这样:</p>
<p><span class="filename">文件名Cargo.toml</span></p>
<pre><code class="language-toml">[package]
name = &quot;guessing_game&quot;
</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: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata
</code></pre>
<p>这个错误是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。在 <em>Cargo.toml</em> 中添加通常是一两句话的描述,因为它将在搜索结果中和你的 crate 一起显示。对于 <code>license</code> 字段,你需要一个 <strong>license 标识符值</strong><em>license identifier value</em>)。<a href="http://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 = &quot;guessing_game&quot;
license = &quot;MIT&quot;
</code></pre>
<p>如果你希望使用不存在于 SPDX 的 license则需要将 license 文本放入一个文件,将该文件包含进项目中,接着使用 <code>license-file</code> 来指定文件名而不是使用 <code>license</code> 字段。</p>
<p>关于项目所适用的 license 指导超出了本书的范畴。很多 Rust 社区成员选择与 Rust 自身相同的 license这是一个双许可的 <code>MIT OR Apache-2.0</code>。这个实践展示了也可以通过 <code>OR</code> 分隔为项目指定多个 license 标识符。</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 = &quot;guessing_game&quot;
version = &quot;0.1.0&quot;
edition = &quot;2021&quot;
description = &quot;A fun game where you guess what number the computer has chosen.&quot;
license = &quot;MIT OR Apache-2.0&quot;
[dependencies]
</code></pre>
<p><a href="http://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><em>permanent</em>)。对应版本不可能被覆盖,其代码也不可能被删除。<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 [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="http://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>yanking</em>)某个版本。</p>
<p>撤回某个版本会阻止新项目依赖此版本,不过所有现存此依赖的项目仍然能够下载和依赖这个版本。从本质上说,撤回意味着所有带有 <em>Cargo.lock</em> 的项目的依赖不会被破坏,同时任何新生成的 <em>Cargo.lock</em> 将不能使用被撤回的版本。</p>
<p>为了撤回一个版本的 crate在之前发布 crate 的目录运行 <code>cargo yank</code> 并指定希望撤回的版本。例如,如果我们发布了一个名为 <code>guessing_game</code> 的 crate 的 1.0.1 版本并希望撤回它,在 <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">
<i class="fa fa-angle-left"></i>
</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">
<i class="fa fa-angle-right"></i>
</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">
<i class="fa fa-angle-left"></i>
</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">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script src="ferris.js"></script>
</div>
</body>
</html>