目前为止我们只使用过 Cargo 构建、运行和测试代码这些最基本的功能,不过它还可以做到更多。本章会讨论 Cargo 其他一些更为高级的功能,我们将展示如何:
- 使用发布配置来自定义构建
- 将库发布到 crates.io
- 使用工作空间来组织更大的项目
- 从 crates.io 安装二进制文件
- 使用自定义的命令来扩展 Cargo
Cargo 的功能不止本章所介绍的,关于其全部功能的详尽解释,请查看链接: 文档
采用发布配置自定义构建
- 在 Rust 中 发布配置(
release profiles
)是预定义的、可定制的带有不同选项的配置,他们允许开发者更灵活地控制代码编译的多种选项。每一个配置都彼此相互独立。 - Cargo有两个主要的配置,运行
cargo build
的dev
配置和运行cargo build --release
的release
配置。 dev
配置被定义为开发时的好的默认配置,release
配置则有着良好的发布构建的默认配置。- 这些配置名称可能很眼熟,因为它们出现在构建的输出中:
$ cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
$ cargo build --release
Finished release [optimized] target(s) in 0.0 secs
- 构建输出中的
dev
和release
表明编译器在使用不同的配置 - 当项目的
Cargo.toml
文件中没有任何[profile.*]
部分的时候,Cargo
会对每一个配置都采用默认设置。通过在[profile.*]
对应的部分中增加任何定制的配置,我们可以覆盖任意默认设置的子集。例如,如下是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
- 这会覆盖默认的设置 0。现在运行
cargo build
时,Cargo
将会使用dev
的默认配置加上定制的opt-level
。因为opt-level
设置为 1,Cargo
会比默认进行更多的优化,但是没有发布构建那么多。
将 crate 发布到 Crates.io
- 我们曾经在项目中使用
crates.io
上的包作为依赖,不过你也可以通过发布自己的包来向别人分享代码。crates.io
用来分发包的源代码,所以它主要托管开源代码。/链接: cargo io - Rust 和 Cargo 有一些帮助别人更方便找到和使用你发布的包的功能。我们将介绍一些这样的功能,接着讲到如何发布一个包。
编写有用的文档注释
- 准确的包文档有助于其他用户理解如何以及何时使用他们,所以花一些时间编写文档是值得的,我们讨论过如何使用两斜杠
//
注释Rust
代码。Rust
也有特定的用于文档的注释类型,通常被称为 文档注释(documentation comments
),他们会生成HTML
文档。这些HTML
展示公有API
文档注释的内容,他们意在让对库感兴趣的开发者理解如何 使用 这个crate
,而不是它是如何被实现 的。 - 文档注释使用三斜杠 /// 而不是两斜杆以支持
Markdown
标记来格式化文本。文档注释就位于需要文档的项的之前。示例展示了一个my_crate crate
中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
}
- 这里,我们提供了一个
add_one
函数工作的描述,接着开始了一个标题为Examples
的部分,和展示如何使用add_one
函数的代码。可以运行cargo doc
来生成这个文档注释的HTML
文档。这个命令运行由Rust
分发的工具rustdoc
并将生成的HTML
文档放入target/doc
目录。 - 为了方便起见,运行
cargo doc --open
会构建当前 crate 文档(同时还有所有 crate 依赖的文档)的 HTML 并在浏览器中打开。导航到add_one
函数将会发现文档注释的文本是如何渲染的 - 示例中使用了Markdown标题之下的# Example在HTML中创建了一个以"Examples"为标题的部分。常用的注释有:
Panics
:这个函数可能会panic!
的场景。并不希望程序崩溃的函数调用者应该确保他们不会在这些情况下调用此函数。Errors
:如果这个函数返回Result
,此部分描述可能会出现何种错误以及什么情况会造成这些错误,这有助于调用者编写代码来采用不同的方式处理不同的错误。Safety
:如果这个函数使用unsafe
代码(这会在第 19 章讨论),这一部分应该会涉及到期望函数调用者支持的确保 unsafe 块中代码正常工作的不变条件(invariants)。
文档注释作为测试
- 在文档注释中增加示例代码块是一个清楚的表明如何使用库的方法,这么做还有一个额外的好处:
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
- 现在尝试改变函数或例子来使例子中的
assert_eq!
产生panic
。再次运行cargo test
,你将会看到文档测试捕获到了例子与代码不再同步!
注释包含项的结构
- 还有另一种风格的文档注释,
//!
,为包含注释的项添加文档,而不是为注释之后的项增加文档。通常用于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--
- 注意
//!
的最后一行之后没有任何代码。因为他们以//!
开头而不是///
,这是属于包含此注释的项而不是注释之后项的文档。在这个情况中,包含这个注释的项是src/lib.rs
文件,也就是crate
根文件。这些注释描述了整个crate
。 - 如果运行
cargo doc --open
,将会发现这些注释显示在my_crate
文档的首页,位于crate
中公有项列表之上,如图:
- 位于项之中的文档注释对于描述
crate
和模块特别有用。使用他们描述其容器整体的目的来帮助crate
用户理解你的代码组织。
使用pub use导出合适的公有API
- 之前介绍过如何使用mod关键字将代码组织进模块中,如何使用
pub
关键字将项变为公有,和如何使用use
关键字将项引入作用域。然而你开发时候使用的文件架构可能并不方便用户。你的结构可能是一个包含多个层级的分层结构,不过这对于用户来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦要使用use my_crate::some_module::another_module::UsefulType;
而不是use my_crate::UsefulType;
来使用类型。 - 公有 API 的结构是你发布
crate
时主要需要考虑的。crate
用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。 - 好消息是,即使文件结构对于用户来说 不是 很方便,你也无需重新安排内部组织:你可以选择使用
pub use
重导出(re-export)项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置,好像它就定义在这个新位置一样。 - 例如,假设我们创建了一个描述美术信息的库
art
。这个库中包含了一个有两个枚举PrimaryColor
和SecondaryColor
的模块kinds
,以及一个包含函数mix
的模块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::Orange
}
}
- cargo doc生成的crate文档如图所示:
- 注意
PrimaryColor
和SecondaryColor
类型、以及mix函数都没有列出在首页,必须使用kinds
或者utils
才能看到。 - 另一个依赖这个库的
crate
需要use
语句来导入art
中的项,这包含指定其当前定义的模块结构。示例展示了一个使用art
crate
中PrimaryColor
和mix
项的 crate 的例子:
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
- 示例,使用
art crate
代码的作者不得不搞清楚PrimaryColor
位于kinds
模块、mix
位于utils
模块。art crate
的模块结构对编写它的开发者更有意义,而不是使用者。对尝试如何使用它的人来说,其内部的kinds
模块和utils
模块的组织结构并没有提供任何有价值的信息。art crate
的模块结构因不得不搞清楚所需的内容在何处,以及必须在use
语句中指定模块名称而显得混乱和不便。 - 为了从公有 API 中去掉 crate 的内部组织,我们可以采用示例中的 art crate 并增加 pub use 语句来重导出项到顶层结构,如示例所示:
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--
}
pub mod utils {
// --snip--
}
- 或者使用下面的方法进行使用:
use art::PrimaryColor;
use art::mix;
fn main() {
// --snip--
}
-
对于有很多嵌套模块的情况,使用
pub use
将类型重导出到顶级结构对于使用crate
的人来说将会是大为不同的体验。 -
创建一个有用的公有 API 结构更像是一门艺术而非科学,你可以反复检视他们来找出最适合用户的 API。
pub use
提供了解耦组织crate
内部结构和与终端用户体现的灵活性。观察一些你所安装的crate
的代码来看看其内部结构是否不同于公有 API。
创建Crates.io账号
- 在你可以发布任何
crate
之前,需要在crates.io
上注册账号并获取一个 API token。为此,访问位于crates.io
的首页并使用GitHub
账号登陆。(目前 GitHub 账号是必须的,不过将来该网站可能会支持其他创建账号的方法)一旦登陆之后,查看位于 链接: https://crates.io/me/ 的账户设置页面并获取 API token。接着使用该 API token 运行 cargo login 命令,像这样:
$ cargo login abcdefghijklmnopqrstuvwxyz012345
- 这个命令会通知
Cargo
你的API token
并将其储存在本地的~/.cargo/credentials
文件中。注意这个token
是一个 秘密(secret)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即到crates.io
重新生成这个token
。
发布新crate之前
- 有了账号之后,比如说你已经有一个希望发布的
crate
。在发布之前,你需要在crate
的Cargo.toml
文件的[package]
部分增加一些本crate
的元信息(metadata)
。 - 首先
crate
需要一个唯一的名称。虽然在本地开发crate
时,可以使用任何你喜欢的名称。不过crates.io
上的crate
名称遵守先到先得的分配原则。一旦某个crate
名称被使用,其他人就不能再发布这个名称的crate
了。请在网站上搜索你希望使用的名称来找出它是否已被使用。如果没有,修改Cargo.toml
中[package]
里的名称为你希望用于发布的名称,像这样: - 文件名:Cargo.toml
[package]
name = "guessing_game"
- 即使你选择了一个唯一的名称,如果此时尝试运行
cargo publish
发布该 crate 的话,会得到一个警告接着是一个错误:
$ cargo publish
Updating registry `https://github.com/rust-lang/crates.io-index`
warning: manifest has no description, license, license-file, documentation,
homepage or repository.
--snip--
error: api errors: missing or empty metadata fields: description, license.
- 这是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。为了修正这个错误,需要在
Cargo.toml
中引入这些信息。 - 描述通常是一两句话,因为它会出现在 crate 的搜索结果中和 crate 页面里。对于
license
字段,你需要一个license
标识符值(license identifier value)
。Linux 基金会的Software Package Data Exchange (SPDX)
列出了可以使用的标识符。例如,为了指定 crate 使用MIT License
,增加MIT
标识符:
[package]
name = "guessing_game"
license = "MIT"
- 如果你希望使用不存在于
SPDX
的license
,则需要将license
文本放入一个文件,将该文件包含进项目中,接着使用license-file
来指定文件名而不是使用license
字段。
关于项目所适用的 license
指导超出了本书的范畴。很多 Rust 社区成员选择与 Rust 自身相同的 license
,这是一个双许可的 MIT OR Apache-2.0
。这个实践展示了也可以通过 OR 分隔为项目指定多个 license
标识符。
- 那么,有了唯一的名称、版本号、由
cargo new
新建项目时增加的作者信息、描述和所选择的 license,已经准备好发布的项目的Cargo.toml
文件可能看起来像这样:
[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"
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 token
,为crate
选择了一个名字,并指定了所需的元数据,你已经准备好发布了!发布crate
会上传特定版本的crate
到crates.io
以供他人使用。 - 发布
crate
时请多加小心,因为发布是 永久性的(permanent
)。对应版本不可能被覆盖,其代码也不可能被删除。crates.io
的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖crates.io
中的crate
的项目都能一直正常工作。而允许删除版本没办法达成这个目标。然而,可以被发布的版本号却没有限制。 - 再次运行
cargo publish
应该能成功!:
$ cargo publish
Updating registry `https://github.com/rust-lang/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.19 secs
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
- 恭喜!你现在向 Rust 社区分享了代码,而且任何人都可以轻松的将你的
crate
加入他们项目的依赖。
发布新的crate版本
- 当你修改了
crate
并准备好发布新版本时,改变Cargo.toml
中version
所指定的值。请使用 语义化版本规则 来根据修改的类型决定下一个版本号。接着运行cargo publish
来上传新版本。
使用cargo yank从Cartes.io撤回版本
- 虽然你不能删除之前版本的
crate
,但是可以阻止任何将来的项目将他们加入到依赖中。这在某个版本因为这样或那样的原因被破坏的情况很有用。对于这种情况,Cargo
支持 撤回(yanking
)某个版本。 - 撤回某个版本会阻止新项目开始依赖此版本,不过所有现存此依赖的项目仍然能够下载和依赖这个版本。从本质上说,撤回意味着所有带有
Cargo.lock
的项目的依赖不会被破坏,同时任何新生成的Cargo.lock
将不能使用被撤回的版本。 - 为了撤回一个
crate
,运行cargo yank
并指定希望撤回的版本:
$ cargo yank --vers 1.0.1
- 也可以撤销撤回操作,并允许项目可以再次开始依赖某个版本,通过在命令上增加
--undo
:
$ cargo yank --vers 1.0.1 --undo
- 撤回 并没有 删除任何代码。举例来说,撤回功能并不意在删除不小心上传的秘密信息。如果出现了这种情况,请立即重新设置这些秘密信息。
Cargo工作空间
- 我们构建一个包含二进制
crate
和库crate
的包。你可能会发现,随着项目开发的深入,库crate
持续增大,而你希望将其进一步拆分成多个库crate
。对于这种情况,Cargo
提供了一个叫 工作空间(workspaces)的功能,它可以帮助我们管理多个相关的协同开发的包。
创建工作空间
- 工作空间 是一系列共享同样的
Cargo.lock
和输出目录的包。让我们使用工作空间创建一个项目 —— 这里采用常见的代码以便可以关注工作空间的结构。有多种组织工作空间的方式;我们将展示一个常用方法。我们的工作空间有一个二进制项目和两个库。二进制项目会提供主要功能,并会依赖另两个库。一个库会提供add_one
方法而第二个会提供add_two
方法。这三个crate
将会是相同工作空间的一部分。让我们以新建工作空间目录开始:
mkdir add
cd add
- 接着在
add
目录中,创建Cargo.toml
文件。这个Cargo.toml
文件配置了整个工作空间。它不会包含[package]
或其他我们在Cargo.toml
中见过的元信息。相反,它以[workspace]
部分作为开始,并通过指定adder
的路径来为工作空间增加成员,如下会加入二进制crate
:
[workspace]
members = [
"adder",
]
- 接下来,在
add
目录运行cargo new
新建adder
二进制crate
:
$ cargo new adder
Created binary (application) `adder` project
- 到此为止,可以运行
cargo build
(在add目录下运行即可)来构建工作空间。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
有其自己的target
目录,为了在自己的target
目录中生成构建结果,工作空间中的每一个crate
都不得不相互重新编译其他crate
。通过共享一个target
目录,工作空间可以避免其他crate
多余的重复构建。
在工作空间中创建第二个crate
- 接下来,让我们在工作空间中指定另一个成员
crate
。这个crate
位于add-one
目录中,所以修改顶级Cargo.toml
为也包含add-one
路径(此添加过程需要一个一个添加):
[workspace]
members = [
"adder",
"add-one",
]
- 接着新生成add-one库:
$ cargo new add-one --lib
Created library `add-one` project
- 现在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
}
- 现在工作空间中有了一个库
crate
,让adder
依赖库crate
add-one
。首先需要在adder/Cargo.toml
文件中增加add-one
作为路径依赖:
[dependencies]
add-one = { path = "../add-one" }
cargo
并不假定工作空间中的Crates
会相互依赖,所以需要明确表明工作空间中crate
的依赖关系。- 接下来,在
adder crate
中使用add-one crate
的函数add_one
。打开adder/src/main.rs
在顶部增加一行use
将新add-one
库crate
引入作用域。接着修改main
函数来调用add_one
函数,
use add_one;
fn main() {
let num = 10;
println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
}
- 在
add
目录中运行cargo build
来构建工作空间!
$ cargo build
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.68 secs
- 为了在顶层
add
目录运行二进制crate
,需要通过-p
参数和包名称来运行cargo run
指定工作空间中我们希望使用的包:
$ cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
- 注意使用
cargo run --help
可以用来进行查看命令帮助。 - 这会运行
adder/src/main.rs
中的代码,其依赖add-one crate
在工作空间中依赖外部crate
- 还需注意的是工作空间只在根目录有一个
Cargo.lock
,而不是在每一个crate
目录都有Cargo.lock
。这确保了所有的crate
都使用完全相同版本的依赖。如果在Cargo.toml
和add-one/Cargo.toml
中都增加rand crate
,则Cargo
会将其都解析为同一版本并记录到唯一的Cargo.lock
中。使得工作空间中的所有crate
都使用相同的依赖意味着其中的crate
都是相互兼容的。让我们在add-one/Cargo.toml
中的[dependencies]
部分增加rand crate
以便能够在add-one crate
中使用rand crate
:
[dependencies]
rand = "0.5.5"
- 现在就可以在
add-one/src/lib.rs
中增加use rand
; 了,接着在add
目录运行cargo build
构建整个工作空间就会引入并编译rand crate
:
$ cargo build
Updating crates.io index
Downloaded rand v0.5.5
--snip--
Compiling rand v0.5.5
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs
- 现在顶级的
Cargo.lock
包含了add-one
的rand
依赖的信息。然而,即使rand
被用于工作空间的某处,也不能在其他crate
中使用它,除非也在他们的Cargo.toml
中加入rand
。例如,如果在顶级的adder crate
的adder/src/main.rs
中增加use rand
;,会得到一个错误:
$ cargo build
Compiling adder v0.1.0 (file:///projects/add/adder)
error: use of unstable library feature 'rand': use `rand` from crates.io (see
issue #27703)
--> adder/src/main.rs:1:1
|
1 | use rand;
- 为了修复这个错误,修改顶级
adder crate
的Cargo.toml
来表明rand
也是这个crate
的依赖。构建adder crate
会将rand
加入到Cargo.lock
中adder
的依赖列表中,但是这并不会下载rand
的额外拷贝。Cargo
确保了工作空间中任何使用rand
的crate
都采用相同的版本。在整个工作空间中使用相同版本的rand
节省了空间,因为这样就无需多个拷贝并确保了工作空间中的crate
将是相互兼容的。 Cargo.lock
:
[[package]]
name = "add-one"
version = "0.1.0"
dependencies = [
"rand",
]
[[package]]
name = "adder"
version = "0.1.0"
dependencies = [
"add-one",
"rand",
]
为工作空间增加测试
- 作为另一个提升,让我们为
add_one crate
中的add_one::add_one
函数增加一个测试:
pub fn add_one(x: i32) -> i32 {
x + 1
}
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
}
- 在顶级
add
目录运行cargo test
:
$ cargo test
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
Running target/debug/deps/add_one-f0253159197f7841
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/adder-f88af9d2cc175a5e
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests add-one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- 输出的第一部分显示
add-one crate
的it_works
测试通过了。下一个部分显示adder crate
中找到了 0 个测试,最后一部分显示add-one crate
中有 0 个文档测试。在像这样的工作空间结构中运行cargo test
会运行工作空间中所有crate
的测试。 - 也可以选择运行工作空间中特定
crate
的测试,通过在根目录使用-p
参数并指定希望测试的crate
名称:
$ cargo test -p add-one
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running target/debug/deps/add_one-b3235fea9a156f74
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests add-one
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- 输出显示了
cargo test
只运行了add-one crate
的测试而没有运行adder crate
的测试。 - 如果你选择向
crates.io
发布工作空间中的crate
,每一个工作空间中的crate
需要单独发布。cargo publish
命令并没有--all
或者-p
参数,所以必须进入每一个crate
的目录并运行cargo publish
来发布工作空间中的每一个crate
。 - 随着项目增长,考虑使用工作空间:每一个更小的组件比一大块代码要容易理解。如果它们经常需要同时被修改的话,将
crate
保持在工作空间中更易于协调他们的改变。
使用cargo install从cargo.io安装二进制文件
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
安装的程序了。
如,之前i/o输入输出中的文件中的ripgrep
中grep
的Rust实现,如果想要安装ripgrep
,使用以下的命令:
$ cargo install ripgrep
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading ripgrep v0.3.2
--snip--
Compiling ripgrep v0.3.2
Finished release [optimized + debuginfo] target(s) in 97.91 secs
Installing ~/.cargo/bin/rg
- 最后一行输出展示了安装的二进制文件的位置和名称,在这里
ripgrep
被命名为rg
。只要你像上面提到的那样将安装目录加入 $PATH,就可以运行rg --help
并开始使用一个更快更Rust
的工具来搜索文件了!
Cargo自定义扩展命令
-
Cargo
的设计使得开发者可以通过新的子命令来对Cargo
进行扩展,而无需修改Cargo
本身。如果 $PATH 中有类似cargo-something
的二进制文件,就可以通过cargo something
来像Cargo
子命令一样运行它。像这样的自定义命令也可以运行cargo --list
来展示出来。能够通过cargo install
向Cargo
安装扩展并可以如内建Cargo
工具那样运行他们是Cargo
设计上的一个非常方便的优点! -
就像是
rg
一样,直接运行即可。