0%

Rust 权威指南(13):进一步认识 Cargo 及 crates.io

目前我们只是使用了一些基础的 Cargo 特性来构建、运行以及测试代码,但是其实它还有相当多的其他功能,这篇文章将讨论这些更为高级的特性。

使用发布配置来定制构建

Rust 中的 发布配置(release profile)是一系列预定义好的配置方案,它们的配置选项各有不同,但都允许程序员对细节进行定制。这些配置方案可以让程序员更好地控制各种编译参数。另外每一套配置都是相互独立的。

Cargo 常用的配置有两种:

  • cargo build 时使用的 dev 配置
  • cargo build –release 时使用的 release 配置

当项目的 Cargo.toml 文件没有任何 [profile.*] 区域时,Cargo 针对每个配置都会有一套可以应用的默认选项。通过为任意的配置添加 [profile.*] 区域时,可以覆盖默认设置的任意子集。

1
2
3
4
5
[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

可以阅读 Cargo 的官方文档 来获取所有可用选项以及在各个配置中的默认值。

将包发布到 crates.io 上

我们也可以发布自己的包来与他人分享代码。由于 crates.io 的包注册表会以源代码的形式来发布你的包,所以由它托管的包大部分都是开源的。

编写有用的文档注释

准确无误的包文档有助于用户理解这个包的用途以及具体的使用方法。Rust 提供了一种特殊的文档注释,以这种方式编写的注释内容可以被用来生成 HTML 文档,这些文档可以展示公共 API 的文档注释内容,它的作用是用于描述当前包的使用方法而不是包内部的实现细节。

  • 使用三斜线 /// 来编写文档注释,可以在文档注释中使用 Markdown 语法来格式化内容。文档注释用于它所说明的条目之前
  • 使用 cargo doc 基于文档注释生成 HTML 文档,该命令调用内置的 rustdoc 工具在 target/doc 目录下生成 HTML 文档
  • 使用 cargo doc --open 来生成并自动在浏览器打开当前包的文档

在文档注释中增加示例(Examples)可以帮助用户理解代码库的使用方式,除此之外,cargo test 在执行时将文档注释中的代码示例作为测试去运行。

还有一种文档注释 //! 可以为包括当前注释的外层条目(而不是紧随注释之后的条目)添加文档。这种文档注释通常被用在包的根文件或模块的根文件上,分别为整个包或整个模块提供文档。

使用 pub use 来导出合适的公共 API

在决定发布一个包时,必须要考虑好如何组织公共 API。包的使用者可能没有你那样熟悉代码结构,一旦包的层次结构过于复杂,用户难以找到他们所需要的部分。

即使代码的内部结构对于用户来说不是特别友好,也不必为解决问题而重新组织代码。可以使用 pub use 来重新导出部分条目,从而建立一套和你内部结构不同的对外结构。重新导出操作会取得某个位置的公共条目并将其公开到另一个位置,就好像该条目原本就定义在新的位置上一样。

当存在较多嵌套模块时,使用 pub use 将类型重新导出到顶层模块可以显著地改善用户体验。pub use 也使得内部结构与外部表现解耦。

创建 crates.io 账户

在发布包之前,需要在 crates.io 上注册一个账户并取得一个 API 令牌(API token)。然后使用 API 令牌执行登录操作:

1
cargo login YOUR_API_TOKEN

该命令会让 Cargo 将你的 API 令牌存入 ~/.cargo/credentials 文件中。

为包添加元数据

在发布之前,需要在 Cargo.toml 文件的 [package] 区域中为这个包添加一些元数据(metadata):

  • 首先包需要一个独一无二的名字,即 name
  • 一个用于介绍包用途的描述,即 description
  • 一个声明使用条款的许可协议,即 licence

发包到 crates.io

发布过程会将一个特定版本的包上传到 crates.io 以供其他人使用。这一操作是永久性的,已经上传的版本无法被覆盖,对应的代码也不能被删除。这种行为也是 crates.io 的一个主要设计目标,它希望能够成为一个永久的代码文档服务器。

运行 cargo publish 命令发布软件包。

发布已有包的新版本

为了在修改代码后发布新的版本,需要修改 Cargo.toml 文件中的 version 字段并重新发布。应该根据语义化版本规则来基于修改的内容决定下一个合理的版本号,再执行 cargo publish 上传新的版本。

使用 cargo yankcargo.io 上移除版本

尽管不能移除一个老版本的包,但是可以阻止未来新项目将它们引用为依赖。这在包的版本因为异常问题而损坏时有用。对于该类场景,cargo yank 可以撤回某个版本。撤回版本可以阻止新的项目来依赖这个版本的包,但现存的依赖于当前版本的项目则依然可以下载和依赖它。更具体的说:

  • 所有已经产生 Cargo.lock 的项目将不会受到撤回操作的影响
  • 而未来所有产生的新 Cargo.lock 文件将不会再使用已经撤回的版本

cargo yank --vers 1.0 可以撤回指定版本,cargo yank --vers 1.0 --undo 可以取消撤回操作,从而允许项目再次开始依赖这个版本。

永远需要牢记,撤回不会删除任何代码

Cargo 工作空间

Cargo 提供了 工作空间(workspace)功能,可以帮助开发者管理多个相互关联且需要协同开发的包。

创建工作空间

工作空间是由共用同一个 Cargo.lock 和输出目录的一系列包所组成。如下展示了一个工作空间的示例,该工作空间包含一个二进制包和两个代码包。首先创建出工作空间的目录:

1
2
mkdir add
cd add

随后在工作空间的目录中添加一个用于配置工作空间的 Cargo.toml 文件。它与之前我们见过的 Cargo.toml 文件不同,它不包含 [package] 区域,也不包含之前使用过的元数据。该文件以 [workspace] 区域开始,该区域允许我们指定二进制包的路径来为工作空间添加成员。

1
2
3
4
5
[workspace]

members = [
"adder",
]

之后使用 cargo new adder 创建 adder 这个二进制包。之后就可以使用 cargo build 来构建整个工作空间。此时目录结构如下:

1
2
3
4
5
6
7
8
9
# tree
.
├── adder
│   ├── Cargo.toml
│   └── src
│   └── main.rs
├── Cargo.lock
├── Cargo.toml
└── target

此时工作空间跟目录下有一个 target 目录用来存放所有成员的编译产出物,相应地,adder 包就没有了自己独立的 target 目录。之所以这样做是因为工作空间的包往往是相互依赖的。如果在每个包下都有自己的 target 目录,则不得不执行各自构建过程中反复编译工作空间的其余包,而共享同一个 target 目录,不同的包就可以避免这些不必要的重复编译过程。

接下里继续增加代码包 adder-one,修改根目录下的 Cargo.toml 文件,在 members 列表中添加 add-one 路径:

1
2
3
4
5
6
[workspace]

members = [
"adder",
"adder-one",
]

接下来生成一个 add-one 的代码包:

1
cargo new --lib adder-one

创建好代码包后,可以让二进制包 adder 依赖于代码包 adder-one,首先需要在 adder/Cargo.toml 中添加 adder-one 的路径作为依赖:

1
2
[dependencies]
adder-one = {path = "../adder-one"}

由于 Cargo 不会主动去假设工作空间中的包会依赖彼此,所以必须显式指出包与包之间的依赖关系。接下来则是在代码中使用 use 语句将 adder-one 包引入作用域:

1
use adder_one;

需要注意的是,整个工作空间只在根目录下有一个 Cargo.lock 文件,而不是在每个包的目录下都有一个 Cargo.lock 文件。该规则确保了所有的内部包都会使用完全相同的依赖版本。

可以在工作空间的根目录下,使用参数 -p 以及指定的包名称来指定要运行或测试的包名称。当你需要将工作空间中的各个包发布到 crates.io 上时,需要将它们分别发布。cargo publish 命令没有提供类似于 --all-p 之类的标记。因此需要手动切换每个包的目录,并对每个包分别执行 cargo publish 来发布。

当多个包经常需要同时修改时,将它们放于同一工作空间下也有助于同步。

crates.io 上安装可执行程序

cargo install 可以安装和使用二进制包,但是它不能代理操作系统的包管理器,该命令指示为了方便 Rust 开发者获得其他人在 crates.io 上分享的工具。另外只能安装那些带有二级制目标(即可执行程序)的包。

所有通过 cargo install 命令安装的二进制文件都会存储在 Rust 安装根目录下的 bin 文件夹中(默认为 ~/.cargo/bin/)。所以为了让安装的工具可以直接运行,需要将该目录放置到系统 PATH 环境变量中。

使用自定义命令扩展 Cargo 的功能

Cargo 允许我们添加子命令来扩展它的功能而无需修改 Cargo 本身。只要在 $PATH 路径中存在 cargo-something,那么久可以运行 cargo something 来运行该二进制文件,就如同它是 cargo 的子命令一样。运行 cargo --list 可以列出所有子命令。

通过这一设计,可以使用 cargo install 来安装扩展,并将这些扩展视为内建的 cargo 命令来运行。