Rust 关于 Cargo 和 Crates.io 的内容
原文链接参考Rust 关于 Cargo 和 Crates.io 的内容,注意Windows和Linux系统的文件路径差异。
采用发布配置自定义构建
在 Rust 中 发布配置(release profiles)文件是预定义和可定制的,Cargo 有两个主要的配置:
- 运行 cargo build 时采用的 dev 配置
- 运行 cargo build --release 的 release 配置
当项目的 Cargo.toml 文件中没有显式增加任何 [profile.*] 部分的时候,Cargo 会对每一个配置都采用默认设置,如下是 dev 和 release 配置的 opt-level 设置的默认值:
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
opt-level 设置控制 Rust 会对代码进行何种程度的优化,配置的值从 0 到 3,越高的优化级别需要更多的时间编译:
- dev 的 opt-level 默认为 0 (牺牲运行性能减少编译时间)
- release 配置的 opt-level 默认为 3 (牺牲编译时间增加运行性能)
可以选择通过在 Cargo.toml 增加不同的值来覆盖任何默认设置,比如想要在开发配置中使用级别 1 的优化,则可以在 Cargo.toml 中增加这两行:
[profile.dev]
opt-level = 1
对于每个配置的设置和其默认值的完整列表,请查看 Cargo 的文档。
将 crate 发布到 Crates.io
编写有用的文档注释
Rust 有特定的用于文档的注释类型,通常被称为 文档注释(documentation comments),它们会生成 HTML 文档——展示公有 API 文档注释的内容。
文档注释使用三斜杠 /// 而不是双斜杠以支持 Markdown 注解来格式化文本,文档注释就位于需要文档的项的之前。
先创建 my_crate crate 项目:
cargo new my_crate --lib
编写 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
}
可以运行 cargo doc 来生成这个文档注释的 HTML 文档,这个命令运行由 Rust 分发的工具 rustdoc 并将生成的 HTML 文档放入 target/doc 目录。
运行 cargo doc --open 会构建当前 crate 文档(同时还有所有 crate 依赖的文档)的 HTML 并在浏览器中打开,导航到 add_one 函数将会发现文档注释的文本如下:
常用(文档注释)部分
除了标题,其他一些 crate 作者经常在文档注释中使用的部分有:
- Panics:这个函数可能会 panic! 的场景。并不希望程序崩溃的函数调用者应该确保他们不会在这些情况下调用此函数。
- Errors:如果这个函数返回 Result,此部分描述可能会出现何种错误以及什么情况会造成这些错误,这有助于调用者编写代码来采用不同的方式处理不同的错误。
- Safety:如果这个函数使用 unsafe 代码,这一部分应该会涉及到期望函数调用者支持的确保 unsafe 块中代码正常工作的不变条件(invariants)。
文档注释作为测试
在文档注释中增加示例代码块是一个清楚的表明如何使用库的方法,cargo test 也会像测试那样运行文档中的示例代码,尝试运行 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
注释包含项的结构
文档注释风格 //! 为包含注释的项,而不是位于注释之后的项增加文档,这通常用于 crate 根文件(通常是 src/lib.rs)或模块的根文件为 crate 或模块整体提供文档。
为了增加描述包含 add_one 函数的 my_crate crate 目的的文档,可以在 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--
运行 cargo doc --open,将会发现这些注释显示在 my_crate 文档的首页,位于 crate 中公有项列表之上:
使用 pub use 导出合适的公有 API
公有 API 的结构是你发布 crate 时主要需要考虑的,可以选择使用 pub use 重导出(re-export) 项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置,好像它就定义在这个新位置一样。
一个库 art 其组织包含 kinds 和 utils 模块:
//! # 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--
SecondaryColor::Green //无返回时会报错
}
}
cargo doc 所生成的 crate 文档首页如下图所示:
注意:* PrimaryColor 和 SecondaryColor 类型、以及 mix 函数都没有在首页中列出**,我们必须点击 kinds 或 utils 才能看到它们。*
一个通过导出内部结构使用 art crate 中项的 crate:
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
为了从公有 API 中去掉 crate 的内部组织,可以采用如下的 art crate 并增加 pub use 语句来重导出项到顶层结构:
//! # 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--
}
pub mod utils {
// --snip--
}
现在此 crate 由 cargo doc 生成的 API 文档会在首页列出重导出的项以及其链接,如下所示:
art crate 的用户可以使用下面更为方便的结构:
puse art::mix;
use art::PrimaryColor;
fn main() {
// --snip--
}
pub use 的另一个常见用法是重导出当前 crate 的依赖的定义使其 crate 定义变成你 crate 公有 API 的一部分。
创建 Crates.io 账号
发布任何 crate 之前,需要在 crates.io 上注册账号并获取一个 API token,接着使用该 API token 运行 cargo login 命令:
cargo login abcdefghijklmnopqrstuvwxyz012345
这个命令会通知 Cargo 你的 API token 并将其储存在本地的 ~/.cargo/credentials 文件中。
向新 crate 添加元信息
在发布之前,需要在 crate 的 Cargo.toml 文件的 [package] 部分增加一些本 crate 的元信息(metadata)。
crate 需要一个唯一的名称,crates.io 上的 crate 名称遵守先到先得的分配原则。如果名称没有被使用,修改 Cargo.toml 中** [package]** 里的名称:
[package]
name = "guessing_game"
需要一个 license 标识符值(license identifier value),[Linux 基金会的 Software Package Data Exchange (SPDX)] (https://spdx.org/licenses/)列出了可以使用的标识符。为了指定 crate 使用 MIT License,增加 MIT 标识符:
[package]
name = "guessing_game"
license = "MIT"
如果使用不存在于 SPDX 的 license,则需要将 license 文本放入一个文件并将该文件包含进项目中,接着使用 license-file 来指定文件名而不是使用 license 字段。
有了唯一的名称、版本号、由 cargo new 新建项目时增加的作者信息、描述和所选择的 license,已经准备好发布的项目的 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/) 的文档 描述了其他可以指定的元信息。
发布到 Crates.io
发布是 永久性的(permanent),对应版本不可能被覆盖,其代码也不可能被删除,可以被发布的版本号却没有限制。
再次运行 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)
发布现存 crate 的新版本
修改了 crate 并准备好发布新版本时,改变 Cargo.toml 中 version 所指定的值,接着运行 cargo publish 来上传新版本。
请使用 语义化版本规则 来根据修改的类型决定下一个版本号。
使用 cargo yank 从 Crates.io 弃用版本
Cargo 支持 撤回(yanking)某个版本,撤回某个版本会阻止新项目依赖此版本,不过所有现存此依赖的项目仍然能够下载和依赖这个版本。
从本质上说,撤回意味着所有带有 Cargo.lock 的项目的依赖不会被破坏,同时任何新生成的 Cargo.lock 将不能使用被撤回的版本。
为了撤回一个版本的 crate,在之前发布 crate 的目录运行 cargo yank 并指定希望撤回的版本:
cargo yank --vers 1.0.1
也可以撤销撤回操作,并允许项目可以再次开始依赖某个版本,通过在命令上增加 --undo:
cargo yank --vers 1.0.1 --undo
撤回功能并不能删除不小心上传的秘密信息,如果出现了这种情况,请立即重新设置这些秘密信息。
Cargo 工作空间
创建工作空间
工作空间 是一系列共享同样的 Cargo.lock 和输出目录的包,创建一个简单是示例:
- 工作空间有一个二进制项目和两个库,二进制项目会提供主要功能并会依赖另两个库
- 一个库会提供 add_one 方法而第二个会提供 add_two 方法
首先新建工作空间目录:
mkdir add
cd add
接着在 add 目录中创建 Cargo.toml 文件,这个 Cargo.toml 文件配置了整个工作空间。它以 [workspace]
部分作为开始,并通过指定 adder 的路径来为工作空间增加成员:
[workspace]
members = [
"adder",
]
powershell命令如下:
@"
[workspace]
members = [
"adder",
]
"@ | Out-File -Encoding UTF8 Cargo.toml
接下来在 add 目录运行 cargo new 新建 adder 二进制 crate:
cargo new adder
运行 cargo build 来构建工作空间,add 目录中的文件如下:
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
- 工作空间在顶级目录有一个 target 目录;adder 并没有自己的 target 目录。
- 进入 adder 目录运行 cargo build,构建结果也位于 add/target 而不是 add/adder/target。
在工作空间中创建第二个包
在工作空间中指定另一个成员 crate,这个 crate 位于 add_one 目录中。修改顶级 Cargo.toml 为也包含 add_one 路径:
[workspace]
members = [
"adder",
"add_one",
]
接着新生成一个叫做 add_one 的库:
cargo new add_one --lib
现在 add 目录应该有如下目录和文件:
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
在 add_one/src/lib.rs 文件中,增加一个 add_one 函数:
pub fn add_one(x: i32) -> i32 {
x + 1
}
有了二进制 adder 依赖库 crate add_one,需要在 adder/Cargo.toml 文件中增加 add_one 作为路径依赖:
[dependencies]
add_one = { path = "..\\add_one" }
接下来,在 adder crate 中使用( add_one crate 中的)函数 add_one:
//将新 add_one 库 crate 引入作用域
use add_one;
fn main() {
let num = 10;
//调用 add_one 函数
println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}
在 add 目录中运行 cargo build 来构建工作空间:
cargo build
为了在顶层 add 目录运行二进制 crate,可以通过 -p 参数和包名称来运行 cargo run 指定工作空间中希望使用的包:
cargo run -p adder
这会运行 adder/src/main.rs 中的代码,其依赖 add_one crate。
在工作空间中依赖外部包
还需注意的是工作空间只在根目录有一个 Cargo.lock,这确保了所有的 crate 都使用完全相同版本的依赖。如果在 Cargo.toml 和 add_one/Cargo.toml 中都增加 rand crate,则 Cargo 会将其都解析为同一版本并记录到唯一的 Cargo.lock 中。
在 add_one/Cargo.toml 中的 [dependencies]
部分增加 rand crate 以便能够在 add_one crate 中使用 rand crate:
[dependencies]
rand = "0.8.5"
在 add_one/src/lib.rs 中增加 use rand;
,接着在 add 目录运行 cargo build 构建整个工作空间:
cargo build
如果想在顶级的 adder crate 的 adder/src/main.rs 中增加 use rand;
,需要修改顶级 adder crate 的 Cargo.toml 来表明 rand 也是这个 crate 的依赖。构建 adder crate 会将 rand 加入到 Cargo.lock 中 adder 的依赖列表中,但是这并不会下载 rand 的额外拷贝。
为工作空间增加测试
为 add_one crate 中的 add_one::add_one 函数增加一个测试:
修改 add_one/src/lib.rs 内容如下:
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));
}
}
在顶级 add 目录运行 cargo test,在像这样的工作空间结构中运行 cargo test 会运行工作空间中所有 crate 的测试:
cargo test
可以选择运行工作空间中特定 crate 的测试,通过在根目录使用 -p 参数并指定希望测试的 crate 名称:
cargo test -p add_one
如果向 crates.io发布工作空间中的 crate,每一个工作空间中的 crate 需要单独发布,可以通过 -p 参数并指定期望发布的 crate 名来发布工作空间中的某个特定的 crate。
使用 cargo install 安装二进制文件
cargo install 命令用于在本地安装和使用二进制 crate,意在作为一个方便 Rust 开发者们安装其他人已经在 crates.io 上共享的工具的手段,只有拥有二进制目标文件的包能够被安装。
二进制目标 文件是在 crate 有 src/main.rs 或者其他指定为二进制文件时所创建的可执行程序,这不同于自身不能执行但适合包含在其他程序中的库目标文件,通常 crate 的 README 文件中有该 crate 是库、二进制目标还是两者兼有的信息。
所有来自 cargo install 的二进制文件都安装到 Rust 安装根目录的 bin 文件夹中。如果你是使用 rustup.rs 来安装 Rust 且没有自定义任何配置,这个目录将是 $HOME/.cargo/bin
。确保将这个目录添加到 $PATH 环境变量中就能够运行通过 cargo install 安装的程序了。
前面提到的叫做 ripgrep 的用于搜索文件的 grep 的 Rust 实现,安装 ripgrep 运行如下:
cargo install ripgrep
Cargo 自定义扩展命令
如果 $PATH 中有类似 cargo-something 的二进制文件,就可以通过 cargo something 来像 Cargo 子命令一样运行它,像这样的自定义命令也可以运行 cargo --list 来展示出来。能够通过 cargo install 向 Cargo 安装扩展并可以如内建 Cargo 工具那样运行它们是 Cargo 设计上的一个非常方便的优点!