将 Crate 发布到 Crates.io
我们已经使用过来自 crates.io的包作为我们项目的依赖项,但您也可以通过发布自己的包与其他人分享您的代码。位于 crates.io的 crate 注册中心会分发您的软件包的源代码,因此它主要托管开源代码。
Rust 和 Cargo 具有使您发布的软件包更容易被人们查找和使用的功能。接下来我们将讨论其中的一些功能,然后解释如何发布软件包。
编写有用的文档注释
准确地记录您的软件包将帮助其他用户了解如何以及何时使用它们,因此值得花时间编写文档。在第 3 章中,我们讨论了如何使用两个斜杠 //
注释 Rust 代码。Rust 还为文档提供了一种特殊的注释,方便地称为文档注释,它会生成 HTML 文档。HTML 显示文档注释中针对公共 API 项的内容,这些 API 项旨在为对如何使用您的 crate 而不是您的 crate 如何实现感兴趣的程序员提供信息。
文档注释使用三个斜杠 ///
而不是两个,并支持 Markdown 表示法来格式化文本。将文档注释放在它们所记录的项之前。清单 14-1 显示了名为 my_crate
的 crate 中 add_one
函数的文档注释。
/// 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) -> i32 {
x + 1
}
在这里,我们提供了 add_one
函数功能的描述,以 Examples
标题开头,然后提供了演示如何使用 add_one
函数的代码。我们可以通过运行 cargo doc
从此文档注释生成 HTML 文档。此命令运行随 Rust 一起分发的 rustdoc
工具,并将生成的 HTML 文档放入 target/doc 目录。
为了方便起见,运行 cargo doc --open
将为当前 crate 的文档(以及您所有 crate 依赖项的文档)构建 HTML,并在 Web 浏览器中打开结果。导航到 add_one
函数,您将看到文档注释中的文本是如何呈现的,如图 14-1 所示

图 14-1:add_one
函数的 HTML 文档
常用部分
我们在清单 14-1 中使用了 # Examples
Markdown 标题在 HTML 中创建了一个标题为“Examples”的部分。以下是 crate 作者通常在其文档中使用的其他一些部分
- Panics:被记录的函数可能发生 panic 的情况。不希望其程序 panic 的函数调用者应确保他们不会在这些情况下调用该函数。
- Errors:如果函数返回
Result
,则描述可能发生的错误类型以及可能导致返回这些错误的条件可能对调用者有所帮助,这样他们就可以编写代码以不同的方式处理不同的错误类型。 - Safety:如果函数调用是
unsafe
的(我们在第 19 章中讨论了不安全性),则应有一个部分解释该函数为什么不安全,并涵盖该函数期望调用者维护的不变性。
大多数文档注释不需要所有这些部分,但这是一个很好的清单,可以提醒您用户将感兴趣了解的代码方面。
作为测试的文档注释
在文档注释中添加示例代码块可以帮助演示如何使用您的库,这样做还有一个额外的好处:运行 cargo test
会将文档中的代码示例作为测试运行!没有什么比带有示例的文档更好。但没有什么比示例不起作用更糟糕了,因为自编写文档以来代码已更改。如果我们运行带有来自清单 14-1 的 add_one
函数文档的 cargo test
,我们将看到测试结果中出现这样的部分
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
现在,如果我们更改函数或示例,使得示例中的 assert_eq!
发生 panic,并再次运行 cargo test
,我们将看到文档测试捕获到示例和代码彼此不同步!
注释包含的项
文档注释 //!
的样式会将文档添加到包含注释的项,而不是添加到注释之后的项。我们通常在 crate 根文件(按照约定是 *src/lib.rs*)或模块内使用这些文档注释,以记录整个 crate 或模块。
例如,要添加描述包含 add_one
函数的 my_crate
crate 用途的文档,我们将以 //!
开头的文档注释添加到 *src/lib.rs* 文件的开头,如清单 14-2 所示
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
// --snip--
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
my_crate
crate 的文档请注意,以 //!
开头的最后一行之后没有任何代码。因为我们以 //!
而不是 ///
开头注释,所以我们正在记录包含此注释的项,而不是跟随此注释的项。在这种情况下,该项是 *src/lib.rs* 文件,即 crate 根。这些注释描述了整个 crate。
当我们运行 cargo doc --open
时,这些注释将显示在 my_crate
文档的首页上,位于 crate 中公共项列表的上方,如图 14-2 所示

图 14-2:my_crate
的呈现文档,包括描述整个 crate 的注释
项内的文档注释对于描述 crate 和模块尤其有用。使用它们来解释容器的整体用途,以帮助您的用户了解 crate 的组织结构。
使用 pub use
导出方便的公共 API
发布 crate 时,您的公共 API 的结构是一个主要考虑因素。使用您的 crate 的人不太熟悉该结构,并且如果您的 crate 具有较大的模块层次结构,则可能难以找到他们想要使用的部分。
在第 7 章中,我们介绍了如何使用 pub
关键字使项公开,以及如何使用 use
关键字将项引入作用域。但是,您在开发 crate 时认为有意义的结构可能对您的用户来说不是很方便。您可能希望在包含多个级别的层次结构中组织您的结构,但是想要使用您在层次结构深处定义的类型的人们可能会发现该类型的存在很困难。他们可能也会对输入 use
my_crate::some_module::another_module::UsefulType;
而不是 use
my_crate::UsefulType;
感到恼火。
好消息是,如果该结构对于其他人从另一个库中使用不方便,则您不必重新安排内部组织:相反,您可以通过使用 pub use
重新导出项,从而形成与您的私有结构不同的公共结构。重新导出将一个位置的公共项在另一个位置公开,就像在另一个位置定义一样。
例如,假设我们创建了一个名为 art
的库来建模艺术概念。在此库中有两个模块:一个包含名为 PrimaryColor
和 SecondaryColor
的两个枚举的 kinds
模块,以及一个包含名为 mix
的函数的 utils
模块,如清单 14-3 所示
//! # 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) -> SecondaryColor {
// --snip--
unimplemented!();
}
}
art
库,其项组织到 kinds
和 utils
模块中图 14-3 显示了 cargo doc
生成的此 crate 文档的首页的外观

图 14-3:art
文档的首页,其中列出了 kinds
和 utils
模块
请注意,PrimaryColor
和 SecondaryColor
类型未在首页上列出,mix
函数也未列出。我们必须单击 kinds
和 utils
才能看到它们。
依赖于此库的另一个 crate 将需要 use
语句,这些语句将 art
中的项引入作用域,并指定当前定义的模块结构。清单 14-4 显示了一个使用 art
crate 中的 PrimaryColor
和 mix
项的 crate 示例
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
art
crate 的项并导出其内部结构的 crate清单 14-4 中代码的作者,它使用了 art
crate,必须弄清楚 PrimaryColor
在 kinds
模块中,而 mix
在 utils
模块中。art
crate 的模块结构对于在 art
crate 上工作的开发人员比对使用它的开发人员更重要。内部结构不包含任何对于尝试了解如何使用 art
crate 的人有用的信息,而是会引起混淆,因为使用它的开发人员必须弄清楚在哪里查找,并且必须在 use
语句中指定模块名称。
要从公共 API 中删除内部组织结构,我们可以修改清单 14-3 中的 art
crate 代码,以添加 pub use
语句以在顶层重新导出项,如清单 14-5 所示
//! # 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--
/// 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 {
// --snip--
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
SecondaryColor::Orange
}
}
pub use
语句以重新导出项cargo doc
为此 crate 生成的 API 文档现在将在首页上列出并链接重新导出的项,如图 14-4 所示,从而使 PrimaryColor
和 SecondaryColor
类型以及 mix
函数更容易找到。

图 14-4:art
文档的首页,其中列出了重新导出的项
art
crate 用户仍然可以像清单 14-4 中所示那样查看和使用清单 14-3 中的内部结构,或者他们可以使用清单 14-5 中更方便的结构,如清单 14-6 中所示
use art::mix;
use art::PrimaryColor;
fn main() {
// --snip--
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
art
crate 的重新导出项的程序在存在许多嵌套模块的情况下,使用 pub use
在顶层重新导出类型可以使使用该 crate 的人的体验产生重大差异。pub use
的另一个常见用途是在当前 crate 中重新导出依赖项的定义,以使该 crate 的定义成为您的 crate 的公共 API 的一部分。
创建有用的公共 API 结构更多是一门艺术而不是一门科学,您可以迭代以找到最适合用户的 API。选择 pub use
使您在内部构建 crate 时具有灵活性,并将内部结构与您呈现给用户的内容分离。查看您已安装的 crate 的某些代码,以查看它们的内部结构是否与它们的公共 API 不同。
设置 Crates.io 帐户
在您可以发布任何 crate 之前,您需要在 crates.io 上创建一个帐户并获取一个 API 令牌。要做到这一点,请访问 crates.io 的主页并通过 GitHub 帐户登录。(目前需要 GitHub 帐户,但该网站将来可能会支持其他创建帐户的方式。)登录后,访问您的帐户设置页面 https://crates.io/me/并检索您的 API 密钥。然后运行 cargo login
命令,并在出现提示时粘贴您的 API 密钥,如下所示
$ cargo login
abcdefghijklmnopqrstuvwxyz012345
此命令将通知 Cargo 您的 API 令牌,并将其本地存储在 ~/.cargo/credentials 中。请注意,此令牌是一个秘密:不要与任何人分享。如果您出于任何原因与任何人分享了它,您应该撤销它,并在 crates.io 上生成一个新的令牌.
向新的包添加元数据
假设您有一个要发布的包。在发布之前,您需要在该包的 Cargo.toml 文件的 [package]
部分添加一些元数据。
您的包需要一个唯一的名称。当您在本地开发一个包时,您可以随意命名它。但是,在 crates.io 上的包名称是按照先到先得的原则分配的。一旦一个包名称被占用,其他人就不能再发布具有相同名称的包。在尝试发布一个包之前,请搜索您想使用的名称。如果该名称已被使用,您将需要找到另一个名称,并在 Cargo.toml 文件中的 [package]
部分下编辑 name
字段,以便使用新名称进行发布,如下所示
文件名: Cargo.toml
[package]
name = "guessing_game"
即使您选择了一个唯一的名称,当您此时运行 cargo publish
来发布该包时,您也会收到一个警告,然后是一个错误
$ cargo publish
Updating crates.io index
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.net.cn/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.net.cn/cargo/reference/manifest.html for how to upload metadata
此错误是因为您缺少一些关键信息:需要提供描述和许可证,以便人们知道您的包的功能以及他们可以在什么条款下使用它。在 Cargo.toml 中,添加一个只有一两个句子的描述,因为它会与您的包一起出现在搜索结果中。对于 license
字段,您需要提供一个许可证标识符值。 Linux 基金会的软件软件包数据交换 (SPDX) 列出了您可以用于此值的标识符。例如,要指定您已使用 MIT 许可证许可了您的包,请添加 MIT
标识符
文件名: Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
如果要使用 SPDX 中没有出现的许可证,您需要将该许可证的文本放在一个文件中,将该文件包含在您的项目中,然后使用 license-file
来指定该文件的名称,而不是使用 license
键。
关于哪个许可证适合您的项目的指导超出了本书的范围。Rust 社区中的许多人使用与 Rust 相同的方式许可他们的项目,即使用 MIT OR Apache-2.0
的双重许可证。这种做法表明您还可以指定多个许可证标识符,以 OR
分隔,以便为您的项目设置多个许可证。
添加了唯一名称、版本、描述和许可证后,一个准备发布的项目的 Cargo.toml 文件可能如下所示
文件名: Cargo.toml
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"
[dependencies]
Cargo 的文档 描述了您可以指定的其他元数据,以确保其他人可以更容易地发现和使用您的包。
发布到 Crates.io
现在您已经创建了一个帐户、保存了您的 API 令牌、为您的包选择了一个名称,并指定了所需的元数据,您就可以发布了!发布包会将特定版本上传到 crates.io供其他人使用。
请注意,发布是永久的。版本永远无法被覆盖,代码也无法删除。 crates.io 的一个主要目标是充当代码的永久档案,以便所有依赖于 crates.io 包的项目构建都能继续工作。允许删除版本会使实现该目标变得不可能。但是,您可以发布的包版本数量没有限制。
再次运行 cargo publish
命令。现在应该成功了
$ 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)
恭喜!您现在已经与 Rust 社区分享了您的代码,任何人都可以轻松地将您的包作为其项目的依赖项添加。
发布现有包的新版本
当您对包进行了更改并准备发布新版本时,您可以更改 Cargo.toml 文件中指定的 version
值并重新发布。使用 语义版本控制规则 根据您所做的更改类型来确定合适的下一个版本号。然后运行 cargo publish
上传新版本。
使用 cargo yank
从 Crates.io 中弃用版本
虽然您无法删除包的先前版本,但您可以阻止任何未来的项目将其添加为新的依赖项。当包版本由于某种原因损坏时,这很有用。在这种情况下,Cargo 支持撤回包版本。
撤回版本会阻止新项目依赖该版本,同时允许所有依赖它的现有项目继续运行。本质上,撤回意味着所有带有 Cargo.lock 的项目都不会中断,并且生成的任何未来的 Cargo.lock 文件都不会使用已撤回的版本。
要撤回一个包的版本,请在您之前发布过的包的目录中,运行 cargo yank
并指定您要撤回的版本。例如,如果我们发布了一个名为 guessing_game
版本为 1.0.1 的包,并且我们想撤回它,我们在 guessing_game
的项目目录中运行
$ cargo yank --vers 1.0.1
Updating crates.io index
Yank [email protected]
通过向命令添加 --undo
,您还可以撤消撤回并允许项目再次开始依赖某个版本
$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank [email protected]
撤回不会删除任何代码。例如,它不能删除意外上传的秘密。如果发生这种情况,您必须立即重置这些秘密。