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

图 14-1:add_one
的 HTML 文档
功能
常用部分
我们使用示例 14-1 中的 # Examples
Markdown 标题在 HTML 中创建了一个标题为 “Examples” 的部分。以下是 crate 作者在他们的文档中常用的一些其他部分:
Panics:被记录的函数可能出现 panic 的情况。如果函数的调用者不希望其程序出现 panic,则应确保他们在这些情况下不要调用该函数。
错误:如果函数返回Result
,则描述可能发生的错误类型以及可能导致返回这些错误的条件可能对调用方有所帮助,以便他们可以编写代码以不同的方式处理不同类型的错误。
安全性:如果函数调用不安全
(我们在第 19 章中讨论不安全),应该有一节解释为什么该函数不安全,并涵盖函数希望调用者维护的不变量。
大多数文档注释不需要所有这些部分,但这是一个很好的清单,可以提醒您用户将有兴趣了解的代码的各个方面。
作为测试的文档注释
在文档注释中添加示例代码块有助于演示如何使用你的库,这样做还有一个额外的好处:运行 cargo test
会将文档中的代码示例作为测试运行!没有什么比带有示例的文档更好的了。但是,没有什么比那些因为自编写文档以来代码发生了变化而无法正常工作的示例更糟糕的了。如果我们使用 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!
在示例中 panics 并再次运行 cargo test
,我们将看到文档测试捕获到示例和代码彼此不同步!
注释包含的项目
doc comment 的样式 !
将文档添加到包含注释的项中,而不是添加到注释后面的项中。我们通常在 crate 根文件(按照约定是 src/lib.rs)或模块中使用这些文档注释来记录 crate 或整个模块。
例如,添加描述 my_crate
目的的文档
crate 中,我们在 src/lib.rs 文件的开头添加以
add_one 开头
的文档注释,如示例 14-2 所示:
文件名: src/lib.rs
//! # 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
}
示例 14-2:整个
my_crate
crate 的文档
请注意,最后一行之后没有任何以 !
开头的代码。由于注释以 !
而不是 开头,因此我们记录的是包含此注释的项目,而不是此注释后面的项目。在本例中,该项是 src/lib.rs 文件,它是 crate 根。这些注释描述了整个 crate。
当我们运行 cargo doc --open
时,这些注释将显示在 my_crate
文档的首页上,在 crate 中公共项目列表的上方,如图 14-2 所示:

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

图 14-3:艺术
文档的扉页
,其中列出了 kinds
和 utils
模块
请注意,PrimaryColor
和 SecondaryColor
类型未在首页上列出,mix
函数也未列出。我们必须单击 kinds
和 utils
才能看到它们。
另一个依赖于这个库的 crate 需要 use
语句,将 art
中的项目引入作用域,并指定
currently defined.示例 14-4 显示了一个使用
PrimaryColor
并混合
Art
Crate 中的项目:
文件名: src/main.rs
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
示例 14-4:使用
art
crate 的物品的 crate,并导出其内部结构
示例 14-4 中代码的作者使用了 art
crate,必须弄清楚 PrimaryColor
在 kinds
模块中,而 mix
在
utils
模块。Art
Crate 的模块结构与使用 Art
Crate 的开发人员更相关,而不是与使用它的人相关。内部结构不包含任何有用的信息,对于试图理解如何使用 art
crate 的人来说,反而会引起混淆,因为使用它的开发人员必须弄清楚在哪里查找,并且必须在 use
语句中指定模块名称。
要从公共 API 中删除内部组织,我们可以修改
art
crate 代码添加 pub use
语句来重新导出顶层的项目,如示例 14-5 所示:
文件名: src/lib.rs
//! # 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
}
}
示例 14-5:添加
pub use
语句以重新导出项目cargo doc
为此 crate 生成的 API 文档现在将列出
并在首页重新导出链接,如图 14-4 所示,使得
PrimaryColor
和 SecondaryColor
类型以及 mix
函数更容易找到。

图 14-4:艺术
文档的扉页
,其中列出了再导出
art
crate 用户仍然可以看到和使用示例 14-3 中的内部结构,如示例 14-4 所示,或者他们可以使用示例 14-5 中更方便的结构,如示例 14-6 所示:
文件名: src/main.rs
use art::mix;
use art::PrimaryColor;
fn main() {
// --snip--
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
示例 14-6:使用
art
crate 中重新导出的项目的程序
在有许多嵌套模块的情况下,使用 pub 在
顶层重新导出类型可以对使用 crate 的人的体验产生重大影响。pub 使用
的另一个常见用途是重新导出当前 crate 中依赖项的定义,以使该 crate 的定义成为 crate 公共 API 的一部分。
创建有用的公共 API 结构与其说是一门科学,不如说是一门艺术,您可以迭代以找到最适合您的用户的 API。选择 pub use
使您可以灵活地在内部构建 crate,并将该内部结构与您呈现给用户的内容分离。查看您安装的一些 crate 代码,看看它们的内部结构是否与它们的公共 API 不同。
设置 Crates.io 帐户
在发布任何 crate 之前,您需要在
crates.io 并获取 API 令牌。为此,请访问主页 crates.io and log
in 通过 GitHub 帐户。(GitHub 帐户目前是必需的,但
该网站将来可能支持其他创建帐户的方式。一次
您已登录,请访问您的账户设置,网址为
https://crates.io/me/ 并检索您的 API 密钥。然后运行 cargo login
命令并在出现提示时粘贴您的 API 密钥,如下所示:
$ cargo login
abcdefghijklmnopqrstuvwxyz012345
此命令将通知 Cargo 您的 API 令牌并将其存储在本地
~/.cargo/credentials 中。请注意,此令牌是一个秘密:请勿与其他任何人共享。如果您出于任何原因与任何人共享它,则应撤销它并在 crates.io 上生成新令牌。
将元数据添加到新 crate
假设您有一个要发布的 crate。在发布之前,你需要在 crate 的 Cargo.toml 的 [package]
部分添加一些元数据
文件。
您的 crate 需要一个唯一的名称。当您在本地处理 crate 时,
你可以随心所欲地给 crate 命名。但是,在
crates.io 按照先到先得的原则分配。一旦 crate 名称被占用,其他人就不能发布具有该名称的 crate。在尝试发布 crate 之前,请搜索要使用的名称。如果该名称已被使用,您将需要找到另一个名称并编辑 [package]
部分下的 Cargo.toml 文件中的 name
字段,以使用新名称进行发布,如下所示:
文件名: Cargo.toml
[package]
name = "guessing_game"
即使你选择了一个唯一的名称,当你此时运行 cargo publish
来发布 crate 时,你会收到一个警告,然后是一个错误:
$ 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
这个错误是因为你错过了一些关键信息:需要描述和许可证,这样人们就会知道你的 crate 是做什么的,以及在什么条件下可以使用它。在 Cargo.toml 中,添加一两句话的描述,因为它将与你的 crate 一起出现在搜索结果中。对于 license
字段,您需要提供许可证标识符值。Linux 的
Foundation 的软件包数据交换 (SPDX) 列出了可用于此值的标识符。例如,要指定您已使用 MIT 许可证许可 crate,请添加 MIT
标识符:
文件名: Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
如果要使用未出现在 SPDX 中的许可证,则需要将该许可证的文本放在文件中,将该文件包含在项目中,然后使用 license-file
指定该文件的名称,而不是使用
许可证
密钥。
关于哪种许可证适合您的项目的指导超出了本书的范围。Rust 社区中的许多人使用 MIT 或 Apache-2.0
的双重许可证,以与 Rust 相同的方式许可他们的项目。此实践表明,您还可以指定多个许可证标识符(以 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 的文档描述了您可以指定的其他元数据,以确保其他人可以更轻松地发现和使用您的 crate。
发布到 Crates.io
现在,您已经创建了一个账户,保存了 API 令牌,并为
的 crate 并指定了所需的元数据,即可发布!
发布 crate 会将特定版本上传到
crates.io 供他人使用。
请小心,因为发布是永久性的。版本永远不能是
overwritten,并且代码无法删除。一个主要目标
crates.io 将充当永久存档
的代码,以便所有依赖于 crate 的项目
crates.io 将继续工作。允许版本删除将使实现该目标变得不可能。但是,您可以发布的 crate 版本数量没有限制。
再次运行 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 社区分享了你的代码,任何人都可以轻松地将你的 crate 添加为他们项目的依赖项。
发布现有 crate 的新版本
当您对 crate 进行了更改并准备发布新版本时,您可以更改 Cargo.toml 文件中指定的 version
值并重新发布。使用 Semantic Versioning 规则,根据您所做的更改类型决定合适的 Next Version Number。然后运行 cargo publish
上传新版本。
弃用 cargo yank
的 Crates.io 版本
虽然您无法删除 crate 的先前版本,但您可以阻止任何将来的项目将它们添加为新的依赖项。当 crate 版本由于某种原因损坏时,这很有用。在这种情况下,Cargo 支持拉动 crate 版本。
撤回版本可以防止新项目依赖于该版本,同时允许依赖它的所有现有项目继续。从本质上讲,一个 yank 意味着所有带有 Cargo.lock 的项目都不会中断,任何将来的
生成的 Cargo.lock 文件不会使用拉取的版本。
要 yank 某个版本的 crate,请在您之前发布的 crate 的目录中,运行 cargo yank 并指定要 yank
的版本。例如,如果我们发布了一个名为 guessing_game
版本 1.0.1 的 crate,并且我们想拉 unk,那么在 guessing_game
的项目目录中,我们将运行:
$ cargo yank --vers 1.0.1
Updating crates.io index
Yank guessing_game@1.0.1
通过在命令中添加 --undo
,您还可以撤消 yank 并允许项目再次根据版本启动:
$ cargo yank --vers 1.0.1 --undo
Updating crates.io index
Unyank guessing_game@1.0.1
抽搐不会删除任何代码。例如,它无法删除意外上传的密钥。如果发生这种情况,您必须立即重置这些密钥。