目录
- [1. 采用发布配置自定义构建](#1. 采用发布配置自定义构建)
- [2. 将crate发布到Crates.io](#2. 将crate发布到Crates.io)
-
- [2.1 编写有用的文档注释](#2.1 编写有用的文档注释)
-
- [2.1.1 常用章节](#2.1.1 常用章节)
- [2.1.2 文档注释作为测试](#2.1.2 文档注释作为测试)
- [2.1.3 注释包含项的结构](#2.1.3 注释包含项的结构)
- [2.2 导出实用的公有API](#2.2 导出实用的公有API)
- [2.3 创建crates.io账号](#2.3 创建crates.io账号)
- [2.4 向新crate添加元数据](#2.4 向新crate添加元数据)
- [2.5 发布到crates.io](#2.5 发布到crates.io)
- [2.6 发布现有crate的新版本](#2.6 发布现有crate的新版本)
- [2.7 使用cargo yank从Crates.io撤回版本](#2.7 使用cargo yank从Crates.io撤回版本)
- [3. Cargo工作空间](#3. Cargo工作空间)
-
- [3.1 创建工作空间](#3.1 创建工作空间)
- [3.2 在工作空间中创建第二个包](#3.2 在工作空间中创建第二个包)
-
- [3.2.1 依赖外部包](#3.2.1 依赖外部包)
- [3.2.2 为工作空间增加测试](#3.2.2 为工作空间增加测试)
- [4. 使用cargo install安装二进制文件](#4. 使用cargo install安装二进制文件)
- [5. Cargo自定义扩展命令](#5. Cargo自定义扩展命令)
- 参考
1. 采用发布配置自定义构建
在Rust中,发布配置release profiles是预定义且可定制的配置文件集,它们包含不同的配置,允许程序员灵活地控制代码编译的多种选项。每一种配置都独立于其他配置。
Cargo有两个主要的配置:运行cargo build时采用的dev配置和运行cargo build --release的release配置。dev配置为开发定义了良好的默认配置,release配置则为发布构建定义了良好的默认配置。
当项目的Cargo.toml文件没有显式增加任何profile.\*部分的时候,Cargo会对每一个配置都采用默认设置。通过增加任何希望定制的配置对应的profile.\*部分,我们可以选择覆盖任意默认设置的子集。如下是dev和release配置的opt-level设置的默认值:
toml
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
opt-level设置控制Rust会对代码进行何种程度的优化。这个配置的值从0到3.越高的优化级别需要更多的时间编译,如果你在进行开发并经常编译,可能会希望在牺牲一些代码性能的情况下减少优化以便编译得快一点。因此dev的opt-level默认为0.当你准备发布时,花费更多时间在编译上则更好。只需要在发布模式编译一次,而编译出来的程序则会运行很多次,所以发布模式用更长的编译时间换取运行更快的代码,因此release配置的默认opt-level为3
2. 将crate发布到Crates.io
我们在项目中使用过crates.io上的包作为依赖,也可以通过发布自己的包来向他人分享代码。crates.io上的crate注册表会分发你包的源代码,因此它主要托管开源代码。
2.1 编写有用的文档注释
准确的包文档有助于其他用户理解如何以及何时使用它们,所以花一些时间编写文档时值得。//可以注释代码,但Rust也有特定的用于文档的注释类型,通常被称为文档注释,它们会生成HTML文档。这些HTML文档展示公有API文档注释的内容,它们意在让对库感兴趣的程序员理解如何使用这个crate,而不是它是如何被实现的。
文档注释使用三条斜杠///,而不是两条斜杠,并且支持使用Markdown标记来格式化文本。将文档注释放在它所说明的项之前。
rust
/// 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的文档构建HTML,并在浏览器中打开结果。定位到add_one函数时,你会看到文档注释中的文本是如何被渲染的:

2.1.1 常用章节
上面的文档展示了使用# Examples Markdown标题在HTML中创建了一个以Examples为标题的部分。其他一些crate作者经常在文档注释中使用的部分有:
Panics:函数在什么情况下可能会panic!。不希望程序panic的调用者应确保不会在这些情况下调用该函数。
Errors:如果函数返回Result,说明可能出现哪些错误,以及什么条件会导致返回这些错误,会有助于调用者编写代码,以不同方式处理不同种类的错误。
Safety:如果调用该函数时unsafe的(后面会将不安全代码),这里应该解释为什么它是不安全的,并说明函数要求调用者维持哪些不变式。
大多数文档注释不需要包含所有这些章节,但这是一份很好的检查清单,可以提醒你关注用户会想了解的内容。
2.1.2 文档注释作为测试
在文档注释中添加示例代码块,有助于展示如何使用你的库,而且还有一个额外的好处:运行cargo test时,文档中的示例代码也会作为测试运行!没有什么比带示例的文档更好了,但也没有什么比示例失败的文档更糟糕了。运行cargo test:

如果我们修改函数或示例中的任意一方,使示例里的assert_eq!触发panic,然后再次运行cargo test,就会看到文档测试捕获了示例与代码不同步的问题。


2.1.3 注释包含项的结构
//!这种文档注释风格为"包含这些注释的项"添加文档,而不是为"位于这些注释之后的项"添加文档。我们通常在crate根文件src/lib.rs或模块内部使用这种文档注释,为整个crate或整个模块编写说明。
为了添加描述包含add_one函数的my crate crate用于的文档,可以在src/lib.rs文件开头加入以//!开头的文档注释:
rust
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
// 省略
最后一行以//!开头的注释后面没有代码。因为我们使用的是//!而不是///,所以这里记录的是"包含这条注释的项"的文档,而不是"紧随这条注释之后的项"的文档。在这里,这个项就是src/lib.rs文件,也就是crate根。这些注释描述的是真个crate。
运行cargo doc --open:

项内部的文档注释特别适合用来描述crate和模块。使用它们来解释这个容器整体的目的,可以帮助用户理解crate的组织方式。
2.2 导出实用的公有API
公有API的结构是你发布crate时主要需要考虑的。crate用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。
第七章介绍了如何使用pub关键字使项公开,以及如何使用use关键字将项引入作用域。不过,在你开发crate时对你来说合理的结果,对用户而言可能并不方便。你可能想把结构体组织成一个包含多层的层级结构,但想使用你定义在深层级中的某个类型的人,可能很难发现它的存在。他们也可能会厌烦不得不写use my_crate::some_module::another_module::UsefulType;,而不是简单的use my_crate::UsefulType;。
好消息是,如果这种结构对外部用户来说并不方便,你也不必重新安排内部组织。你可以使用pub use来重导出项,从而建立一个与私有结构不同的公有结构。重导出会把某个位置的公有项在另一个位置再次公开,就好像它原本就定义在哪里一样。
假设我们创建了一个名为art的库,用来建模艺术概念。在这个库里,有两个模块:kinds模块包含两个枚举PrimaryColor和SecondaryColor,utils模块包含一个名为mix的函数:
rust
//! # 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 {
// 省略
}
}
下面是这个crate生成的文档首页。

注意PrimaryColor和SecondaryColor类型、以及mix函数都没有在首页中列出。我们必须点击kinds或utils才能看到它们。
依赖这个库的另一个crate需要使用use语句,把art中的项引入作用域,同时必须指定当前定义的模块结构。如下所示:
rust
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
写出上面代码的作者必须先弄清楚PrimaryColor在kinds模块中,而mix在utils模块中。art crate的模块结构,对开发art crate的人来说比对使用它的人更有意义。这种内部结构并没有给想理解如何使用art crate的人提供有价值的信息,反而会带来困惑,因为用户必须先搞清楚该去哪里找需要的内容,还要在use语句中写出模块名。
为了从公有API中去掉内部组织细节,在art crate中加入pub use语句,在顶层导出这些项。
rust
//! # 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 {
// 省略
}
pub mod utils {
// 省略
}
运行cargo doc --open之后可以在首页看到重导出项,这使PrimaryColor、SecondaryColor和mix更容易被找到。

art crate的用户仍然可以像网页文档看的一样使用art中的内部结构:
rust
use art::PrimaryColor;
use art::mix;
fn main() {
// 省略
}
在存在很多嵌套模块的情况下,使用pub use将类型重导出到顶层,会显著改善使用这个crate的体验。pub use的另一个用法,是把当前crate的某个依赖中的定义重新导出,让那个crate的定义称为你这个crate公有API的一部分。
创建有用的公有API结构更像一门艺术,而不是科学;你可以不断迭代,找到最适合用户的API。选择pub use能让你在crate内部结构的组织方式上保持灵活,并将其与你呈现给用户的结构解耦。
2.3 创建crates.io账号
在发布任何crate之前,你需要在crates.io上创建账号并获取一个API token。为此请访问crates.io首页并通过Github账号登陆。登录之后,前往https://crates.io/me的账户设置页面获取API key。然后运行cargo login命令,并在提示时粘贴你的API key。
这个命令会把你的API token告诉cargo,并将其保存在本地的~/.cargo/credentials文件中。所有token都是秘密,不应该与任何人分享。如果泄露了,立即前往crates.io撤销并重新生成一个token。
2.4 向新crate添加元数据
比如说你已经有一个希望发布的crate。在发布之前,你需要在crate的Cargo.toml文件的package部分增加一些本crate的元数据。
首先,crate需要一个唯一的名称。虽然在本地开发crate时,可以随意命名,但crates.io上的crate名称遵循先到先得的原则。一但某个crate名称已经被占据,就没有其他人能再用这个名称发布crate。请搜索你想使用的名称,确认它是否已被占用。如果没有,就把Cargo.toml中package里的name字段改成你想发布时使用的名称,如下所示:
toml
[package]
name = "guessing_game"
即使你选择了一个唯一的名称,如果此时尝试运行cargo publish发布该crate的话,会得到一个警告:缺少一些关键信息------关于该crate用途的描述,以及用户可以在什么许可条款下使用它。在Cargo.toml中添加一两句简短描述即可,因为它会在搜索结果中和你的crate一起显示。对于license字段,你需要填写一个许可证标识符值。Linux基金会的Software Package Data Exchange(SPDX)列出了可用的标识符。如果使用MIT License,如下所示:
toml
[package]
name = "guessing_name"
license = "MIT"
如果你想使用SPDX中不存在的许可证,就需要把许可证文本放入一个文件中,将该文件包含到项目里,然后使用license-file指定该文件名,而不是使用license字段。
很多Rust社区成员选择与Rust本身相同的许可证,也就是双许可证MIT OR Apache-2.0。可以使用OR分隔多个许可证标识符,来为项目指定多个许可证。
有了唯一的名称、版本号、由cargo new新建项目时增加的作者信息、描述和所选择的license,已经准备好发布的项目的Cargo.toml文件看起来如下:
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]
2.5 发布到crates.io
在创建了账号、保存了API token、为crate准备好名字以及元数据之后,可以发布了。发布crate会将该crate的某个特定版本上传到crates.io供他人使用。
发布crate时务必小心,发布是永久性的。对应版本无法被覆盖,其代码也无法被删除。crates.io的一个主要目标,是充当代码的永久归档服务器,这样所有依赖crates.io上crate的项目可以一直正常工作。而如果允许删除版本,就无法实现这一目标。不过,可发布的版本号数量并没有限制。
运行cargo publish发布。
2.6 发布现有crate的新版本
当你修改了crate并准备发布新版本时,修改Cargo.toml中version的值。请使用语义化版本控制规则,根据修改的类型决定下一个版本号。然后再次运行cargo publish来上传新版本。
2.7 使用cargo yank从Crates.io撤回版本
虽然你不能删除crate的历史版本,但可以阻止未来的新项目把它加入依赖。这在某个版本因为某种原因损坏时会很用。为此,Cargo支持对某个版本执行撤回yank。
撤回某个版本会阻止新项目依赖这个版本,不过所有已经依赖它的项目仍然可以下载并继续依赖它。从本质上说,撤回意味着:所有已有Cargo.lock的项目都不会因此损坏,而任何新生成的Cargo.lock都不会再使用被撤回的版本。
要撤回crate的某个版本,请在之前发布该crate的目录中运行cargo yank,并指定要撤回的版本。比如,如果我们发布了名为guessing_game的crate的1.0.1版本,并想撤回它,就在guessing_game项目目录中运行:
bash
cargo yank --vers 1.0.1
也可以撤销这次撤回,让项目重新可以依赖该版本,只需在命令中加上--undo:
bash
cargo yank --vers 1.0.1 --undo
撤回不会删除任何代码。例如,撤回功能并不能删除你不小心上传的秘密信息。如果发生了这种情况,请立刻轮换这些秘密信息。
3. Cargo工作空间
Cargo提供了一项叫做工作空间workspace的功能,可以帮助管理多个彼此相关、并行开发的包。
3.1 创建工作空间
工作空间是一组共享同一个Cargo.lock和输出目录的包。
下面创建一个工作空间,包含一个二进制crate和两个库。二进crate提供主要功能,并依赖这两个库。一个库提供add_one函数,另一个库提供add_two函数。三个crate属于同一个工作空间。创建工作空间的目录:
bash
mkdir add
cd add
在add目录中新建Cargo.toml文件,用来配置整个工作空间。这个文件不会有package部分,而是会以workspace部分开头,这样我们就能向工作空间添加成员。我们还会把resolver的值设为"3",以便在工作空间中使用Cargo最新的依赖解析算法。
toml
[workspace]
resolver = "3"
接下来,在add目录运行cargo new新建adder二进制crate:
bash
cargo new adder

在工作空间中运行cargo new时,新创建的包也会被自动加入工作空间Cargo.toml中workspace定义的members键:
toml
[workspace]
resolver = "3"
members = ["adder"]
可以运行cargo build来构建工作空间。

工作空间在顶层只有一个target目录,用来存放编译产物;adder包不会有自己的target目录。即使我们在adder目录中运行cargo build,编译产物也仍会放到add/target,而不是add/adder/target。Cargo之所以这样组织工作空间中的target目录,是因为工作空间中的crate本来就是要彼此依赖的。如果每个crate都有各自的target目录,那么每个crate都不得不重新编译工作空间中的其他crate,才能把产物放进自己的target目录。共享一个target目录可以避免不必要的重复构建。
3.2 在工作空间中创建第二个包
在工作空间中创建另一个成员包,并将其命名为add_one。
bash
cargo new add_one --lib

现在add目录包含如下目录和文件:

在add_one/src/lib.rs中,增加一个add_one函数:
rust
pub fn add_one(x: i32) -> i32 {
x + 1
}
现在可以让二进制包adder依赖包含库的add_one包。首先需要在adder/Cargo.toml中把add_one添加为一个路径依赖:
toml
[dependencies]
add_one = { path = "../add_one" }
Cargo并不会假定工作空间中的crate会彼此依赖,因为我们需要显式声明这些依赖关系。
在adder crate中使用add_one crate里的add_one函数。在adder/src/main.rs中调用add_one:
rust
fn main() {
let num = 10;
println!("Hello, World! {num} plus one is {}!", add_one::add_one(num));
}
在顶层add目录中运行cargo build来构建工作空间。

要从add目录运行这个二进制crate,可以在cargo run时通过-p参数加上包名,指定要运行工作空间中的哪个包:
bash
cargo run -p adder

这会运行adder/src/main.rs中的代码,其依赖add_one crate。
3.2.1 依赖外部包
注意,工作空间只在顶层有一个Cargo.lock文件,而不是让每个crate目录里都各自有一个Cargo.lock。这能确保所有crate使用的都是同一个版本的依赖。如果我们把rand包同时加到adder/Cargo.toml和add_one/Cargo.toml中,Cargo会把它们都解析为同一个rand版本,并把结果记录到唯一的Cargo.lock中。让工作空间中的所有crate使用相同依赖,意味着这些crate会始终彼此兼容。将rand crate加到add_one/Cargo.toml的dependencies部分:
toml
[dependencies]
rand = "0.10.2"
在add_one/src/lib.rs中加入use rand;,然后在add目录中运行cargo build来构建整个工作空间,这会引入并编译rand crate。
顶层的Cargo.lock现在已经包含了add_one依赖rand的信息。不过,即使rand在工作空间的某处被使用,我们也不能直接在工作空间里的其他crate中使用它,除非把rand加到它们各自的Cargo.toml中。

要修复这个错误,必须把rand也加到adder的Cargo.toml中。这样在构建adder包的时候,才会将rand加到Cargo.lock中adder的依赖列表里。Cargo会确保工作空间中每个使用rand的crate都使用同一个版本,只要它们声明的是彼此兼容的rand版本,这样既节省空间,也确保工作空间中的crate彼此兼容。
如果工作空间中的crate为同一个依赖指定了彼此不兼容的版本,Cargo仍然会分别解析它们,但会尽量把版本数量控制得尽可能少。
3.2.2 为工作空间增加测试
为add_one::add_one函数增加一个测试:
rust
pub fn add_one(x: i32) -> i32 {
x + 1
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
在根目录下运行cargo test,会执行工作空间中所有crate的测试:

可以在根目录中通过-p参数并指定想要测试的crate名称:

如果你打算把工作空间中的crate发布到crates.io上,那么工作空间中的每个crate都需要单独发布。和cargo test一样,可以通过-p参数并指定要发布的crate名称,来发布工作空间中的某个特定crate。
4. 使用cargo install安装二进制文件
cargo install命令允许你在本地安装和使用二进制crate。它并不是为了替代系统包管理器,而是为Rust开发者提供一种方便的方式,用来安装他人在crates.io上分享的工具。只有带有二进制目标的包才能被安装。二进制目标是指当crate包含src/main.rs文件,或将其他文件指定为二进制目标时所生成的可运行文件;这与库目标不同,库目标本身不能单独运行,但适合被其他程序接入。通常,crate的README文件会说明它是库、带有二进制目标,还是两者兼有。
所有通过cargo install安装的二进制文件,都会放在安装根目录下的bin文件夹中。请确保这个bin文件夹已经在系统环境变量PATH中。
5. Cargo自定义扩展命令
Cargo的设计允许你用新的子命令来扩展它,而不必修改Cargo本身。如果你的PATH中有一个名为cargo-something的二进制文件,那么你可以像Cargo子命令一样,通过cargo something来运行它。这类自定义命令也会在你运行cargo --list时显示出来。Cargo这种设计带来了一个非常方便的好处:你可以引用cargo install安装扩展,然后像使用Cargo内建工具一样运行它们。