简介
欢迎来到 将一个 crate 发布到 crates.io。本实验是 《Rust 程序设计语言》 的一部分。你可以在 LabEx 中练习你的 Rust 技能。
在本实验中,我们将讨论如何将一个 crate 发布到 crates.io,这是一个分发开源代码的 crate 注册中心,能让人们更轻松地找到并使用你的包。
This tutorial is from open-source community. Access the source code
💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版
欢迎来到 将一个 crate 发布到 crates.io。本实验是 《Rust 程序设计语言》 的一部分。你可以在 LabEx 中练习你的 Rust 技能。
在本实验中,我们将讨论如何将一个 crate 发布到 crates.io,这是一个分发开源代码的 crate 注册中心,能让人们更轻松地找到并使用你的包。
我们已经将来自 https://crates.io 的包用作项目的依赖项,但你也可以通过发布自己的包与其他人共享你的代码。https://crates.io 上的 crate 注册中心会分发你的包的源代码,所以它主要托管开源代码。
Rust 和 Cargo 具有一些特性,能让你发布的包更便于人们查找和使用。接下来我们将讨论其中的一些特性,然后解释如何发布一个包。
准确地为你的包编写文档将帮助其他用户了解如何以及何时使用它们,所以值得花时间来编写文档。在第3章中,我们讨论了如何使用两个斜杠 //
来注释 Rust 代码。Rust 还有一种专门用于文档的注释,方便地称为 文档注释,它会生成 HTML 文档。该 HTML 会显示面向那些想要知道如何 使用 你的 crate 而不是你的 crate 是如何 实现 的程序员的公共 API 项的文档注释内容。
文档注释使用三个斜杠 ///
而不是两个,并支持使用 Markdown 标记来格式化文本。将文档注释放在它们所注释的项之前。清单14-1展示了一个名为 my_crate
的 crate 中 add_one
函数的文档注释。
文件名:src/lib.rs
/// 将给定的数字加一。
///
/// ## 示例
///
/// ```
/// 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
函数的功能,以 示例
为标题开始一个部分,然后提供演示如何使用 add_one
函数的代码。我们可以通过运行 cargo doc
从这个文档注释生成 HTML 文档。这个命令会运行 Rust 附带的 rustdoc
工具,并将生成的 HTML 文档放在 target/doc
目录中。
为了方便起见,运行 cargo doc --open
将为你当前 crate 的文档(以及你的 crate 的所有依赖项的文档)生成 HTML,并在网页浏览器中打开结果。导航到 add_one
函数,你将看到文档注释中的文本是如何呈现的,如图14-1所示。
图14-1:add_one
函数的 HTML 文档
在清单14-1中,我们使用了 ## 示例
这个 Markdown 标题来在 HTML 中创建一个标题为 “示例” 的部分。以下是一些 crate 作者在文档中常用的其他部分:
Result
,描述可能发生的错误类型以及可能导致返回这些错误的条件,这对调用者会有帮助,这样他们就可以编写代码以不同方式处理不同类型的错误。不安全的
(我们将在第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!
导致恐慌,然后再次运行 cargo test
,我们会看到文档测试会捕捉到示例和代码彼此不同步的情况!
文档注释 //!
会为 包含 这些注释的项添加文档,而不是为 跟随 这些注释的项添加文档。我们通常在 crate 根文件(按照惯例是 src/lib.rs
)内部或模块内部使用这些文档注释,来记录整个 crate 或模块。
例如,为了添加描述包含 add_one
函数的 my_crate
crate 的用途的文档,我们在 src/lib.rs
文件开头添加以 //!
开头的文档注释,如清单14-2所示。
文件名:src/lib.rs
//! ## 我的 crate
//!
//! `my_crate` 是一组实用工具,用于使执行某些计算更加便捷。
/// 将给定的数字加一。
--snip--
清单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 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
的库来对艺术概念进行建模。在这个库中有两个模块:一个 kinds
模块,包含两个名为 PrimaryColor
和 SecondaryColor
的枚举;以及一个 utils
模块,包含一个名为 mix
的函数,如清单14-3所示。
文件名:src/lib.rs
//! ## 艺术
//!
//! 一个用于对艺术概念进行建模的库。
pub mod kinds {
/// 根据 RYB 颜色模型的原色。
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// 根据 RYB 颜色模型的间色。
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}
pub mod utils {
use crate::kinds::*;
/// 将两种等量的原色混合以创建一种间色。
pub fn mix(
c1: PrimaryColor,
c2: PrimaryColor,
) -> SecondaryColor {
--snip--
}
}
清单14-3:一个将项组织到 kinds
和 utils
模块中的 art
库
图14-3展示了由 cargo doc
生成的此 crate 文档的首页样子。
图14-3:列出 kinds
和 utils
模块的 art
文档首页
请注意,PrimaryColor
和 SecondaryColor
类型以及 mix
函数都没有列在首页上。我们必须点击 kinds
和 utils
才能看到它们。
另一个依赖此库的 crate 需要使用 use
语句将 art
中的项引入作用域,并指定当前定义的模块结构。清单14-4展示了一个使用 art
crate 中的 PrimaryColor
和 mix
项的 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 中去除内部组织,我们可以修改清单14-3中的 art
crate 代码,添加 pub use
语句以在顶层重新导出项,如清单14-5所示。
文件名:src/lib.rs
//! ## 艺术
//!
//! 一个用于对艺术概念进行建模的库。
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
pub mod kinds {
--snip--
}
pub mod utils {
--snip--
}
清单14-5:添加 pub use
语句以重新导出项
现在,cargo doc
为此 crate 生成的 API 文档将在首页列出并链接重新导出的项,如图14-4所示,这使得 PrimaryColor
和 SecondaryColor
类型以及 mix
函数更容易找到。
图14-4:列出重新导出项的 art
文档首页
art
crate 的用户仍然可以像清单14-4中那样看到并使用清单14-3中的内部结构,或者他们也可以使用清单14-5中更方便的结构,如清单14-6所示。
文件名:src/main.rs
use art::mix;
use art::PrimaryColor;
fn main() {
--snip--
}
清单14-6:一个使用 art
crate 重新导出项的程序
在有许多嵌套模块的情况下,使用 pub use
在顶层重新导出类型会对使用该 crate 的人的体验产生显著影响。pub use
的另一个常见用途是在当前 crate 中重新导出依赖项的定义,以使该 crate 的定义成为你 crate 公共 API 的一部分。
创建一个有用的公共 API 结构更多的是一门艺术而非科学,你可以反复尝试以找到最适合你用户的 API。选择 pub use
会让你在内部构建 crate 的方式上具有灵活性,并将内部结构与呈现给用户的结构解耦。看看你安装的一些 crate 的代码,看看它们的内部结构是否与公共 API 不同。
在发布任何 crate 之前,你需要在 https://crates.io 上创建一个账户并获取一个 API 令牌。要做到这一点,访问 https://crates.io 的主页并通过 GitHub 账户登录。(目前需要 GitHub 账户,但该网站未来可能会支持其他创建账户的方式。)登录后,访问 https://crates.io/me 的账户设置并获取你的 API 密钥。然后使用你的 API 密钥运行 cargo login
命令,如下所示:
cargo login abcdefghijklmnopqrstuvwxyz012345
此命令会将你的 API 令牌告知 Cargo 并将其本地存储在 ~/.cargo/credentials 中。请注意,此令牌是一个 秘密:不要与其他任何人共享。如果你出于任何原因与他人共享了它,你应该撤销它并在 https://crates.io 上生成一个新的令牌。
假设你有一个想要发布的 crate。在发布之前,你需要在 crate 的 Cargo.toml
文件的 [package]
部分添加一些元数据。
你的 crate 需要一个唯一的名称。当你在本地处理一个 crate 时,你可以给它取任何你想要的名字。然而,https://crates.io 上的 crate 名称是先到先得的。一旦一个 crate 名称被占用,其他人就不能再发布使用该名称的 crate 了。在尝试发布 crate 之前,搜索你想要使用的名称。如果该名称已被使用,你将需要找到另一个名称,并编辑 Cargo.toml
文件中 [package]
部分下的 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
字段,你需要给出一个 许可证标识符值。位于 http://spdx.org/licenses 的 Linux 基金会的软件包数据交换 (SPDX) 列出了你可以用于此值的标识符。例如,要指定你已根据 MIT 许可证对你的 crate 进行了许可,添加 MIT
标识符:
文件名:Cargo.toml
[package]
name = "guessing_game"
license = "MIT"
如果你想使用 SPDX 中未出现的许可证,你需要将该许可证的文本放在一个文件中,将该文件包含在你的项目中,然后使用 license-file
来指定该文件的名称,而不是使用 license
键。
关于哪种许可证适合你的项目的指导超出了本书的范围。Rust 社区中的许多人通过使用 MIT OR 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 在 https://doc.rust-lang.org/cargo 的文档描述了你可以指定的其他元数据,以确保其他人能够更轻松地发现和使用你的 crate。
既然你已经创建了账户、保存了 API 令牌、为你的 crate 选择了名称并指定了所需的元数据,那么你就准备好发布了!发布一个 crate 会将特定版本上传到 https://crates.io 供其他人使用。
要小心,因为发布是 永久性的。版本永远不能被覆盖,代码也不能被删除。Crates.io 的一个主要目标是充当代码的永久存档,以便所有依赖于 https://crates.io 上 crate 的项目构建能够继续正常工作。允许删除版本会使实现该目标变得不可能。然而,你可以发布的 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 进行了更改并准备发布新版本时,你需要更改 Cargo.toml
文件中指定的 version
值,然后重新发布。根据你所做更改的类型,使用位于 http://semver.org 的语义化版本控制(Semantic Versioning)规则来确定下一个合适的版本号。然后运行 cargo publish
来上传新版本。
虽然你不能删除 crate 的先前版本,但你可以阻止未来的任何项目将它们作为新的依赖项添加。当 crate 版本由于某种原因出现问题时,这很有用。在这种情况下,Cargo 支持弃用 crate 版本。
弃用 一个版本可防止新项目依赖该版本,同时允许所有依赖它的现有项目继续使用。本质上,弃用意味着所有包含 Cargo.lock 的项目不会中断,并且未来生成的任何 Cargo.lock 文件都不会使用被弃用的版本。
要弃用 crate 的某个版本,在你之前发布的 crate 所在目录中,运行 cargo yank
并指定你要弃用的版本。例如,如果我们发布了一个名为 guessing_game
的 crate 版本 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]
弃用 不会 删除任何代码。例如,它不能删除意外上传的机密信息。如果发生这种情况,你必须立即重置这些机密信息。
恭喜!你已经完成了“将 crate 发布到 Crates.io”实验。你可以在 LabEx 中练习更多实验来提升你的技能。