Cargo 使用指南之代码组织管理

Cargo 是 Rust 软件包管理器。Cargo 可以下载 Rust 软件包的依赖项、编译软件包、制作可分发的软件包,并将它们上传到 Rust 社区的软件包注册中心(Package Registry ) 。

0x01. 快速上手

1.1 创建二进制项目

1
2
cargo new hello_world
# 等价于 cargo new hello_world --bin
1
2
3
4
5
6
7
8
9
cd hello_world/
# 项目结构
tree .
# .
# ├── Cargo.toml
# └── src
# └── main.rs
#
# 2 directories, 2 files
1
2
3
4
// cat src/main.rs
fn main() {
println!("Hello, world!");
}
1
2
3
4
5
6
7
// cat Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

[dependencies]
1
2
3
4
5
6
# 运行
cargo run
# Compiling hello_world v0.1.0 (/home/sec/rust-code/hello_world)
# Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.74s
# Running `target/debug/hello_world`
# Hello, world!

1.2 创建依赖库项目

1
cargo new hello_world --lib
1
2
3
4
5
6
7
8
9
cd hello_world/
# 项目结构
tree .
# .
# ├── Cargo.toml
# └── src
# └── lib.rs
#
# 2 directories, 2 files
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/lib.rs
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
1
2
3
4
5
6
7
// Cargo.toml
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

[dependencies]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 代码测试
cargo test
# Compiling hello_world v0.1.0 (/home/sec/rust-code/hello_world)
# Finished `test` profile [unoptimized + debuginfo] target(s) in 0.51s
# Running unittests src/lib.rs (target/debug/deps/hello_world-75e6e3a0ae56ef19)
#
# running 1 test
# test tests::it_works ... ok
#
# test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
#
# Doc-tests hello_world
#
# running 0 tests
#
# test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
#

1.3 下载项目并构建

1
2
3
4
5
6
7
8
9
10
# 下载
git clone https://github.com/rust-lang/regex.git
# Cloning into 'regex'...
# remote: Enumerating objects: 9712, done.
# remote: Counting objects: 100% (3776/3776), done.
# remote: Compressing objects: 100% (1345/1345), done.
# remote: Total 9712 (delta 2492), reused 3367 (delta 2373), pack-reused 5936 (from 1)
# Receiving objects: 100% (9712/9712), 8.03 MiB | 816.00 KiB/s, done.
# Resolving deltas: 100% (6364/6364), done.
cd regex
1
2
3
4
5
6
7
8
9
10
11
# 构建
cargo build
# Updating `rsproxy-sparse` index
# Locking 54 packages to latest compatible versions
# Adding env_logger v0.9.3 (available: v0.11.5)
# Compiling memchr v2.7.4
# Compiling regex-syntax v0.8.5 (/home/sec/rust-code/regex/regex-syntax)
# Compiling aho-corasick v1.1.3
# Compiling regex-automata v0.4.9 (/home/sec/rust-code/regex/regex-automata)
# Compiling regex v1.11.1 (/home/sec/rust-code/regex)
# Finished `dev` profile [optimized + debuginfo] target(s) in 7.13s

0x02. 核心概念

2.1 Module

  • 模块(Module):可以用来将代码分层地拆分为逻辑单元(模块),并管理它们之间的可见性(公共/私有)
  • 模块中包含:函数、结构、特征、实现块,甚至其他模块。
  • 一个文件可以多个模块,也可以一个文件一个模块,模块可以被认为是真实项目中的代码组织单元

2.2 Crate

  • 板条箱 (Crate) 是 Rust 中的编译单元

2.3 Package

  • 包(package)是一个包含一个或多个 crate 的集合,用于提供一组功能。
  • 一个包(package)包含一个 Cargo.toml 文件,该文件描述了如何构建这些 crate。
  • 一个包(package)可以包含任意多个二进制 crate,但最多只能包含一个库 crate。
  • 一个包(package)必须至少包含一个 crate,无论是库 crate 还是二进制 crate。

2.3.1 Package 目录布局

Cargo 使用约定来放置文件,以便轻松进入新的 Cargo Package

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.
├── Cargo.lock
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── main.rs
│   └── bin/
│ ├── named-executable.rs
│      ├── another-executable.rs
│      └── multi-file-executable/
│      ├── main.rs
│      └── some_module.rs
├── benches/
│   ├── large-input.rs
│   └── multi-file-bench/
│   ├── main.rs
│   └── bench_module.rs
├── examples/
│   ├── simple.rs
│   └── multi-file-example/
│   ├── main.rs
│   └── ex_module.rs
└── tests/
├── some-integration-tests.rs
└── multi-file-test/
├── main.rs
└── test_module.rs

  • Cargo.tomlCargo.lock 存储在包的根目录中(包根目录)。
  • 源代码放在 src 目录中。
  • 默认库文件是 src/lib.rs
  • 默认可执行文件是 src/main.rs
    • 其他可执行文件可以放在 src/bin/ 中。
  • 基准测试放在 benches 目录中。
  • 示例放在 examples 目录中。
  • 集成测试放在 tests 目录中。

2.4 Workspace

工作区(Workspace)是一个或多个包(Package)的集合,这些包称为工作区成员,它们一起进行管理。

0x03. 基础包管理

3.1 包含一个 crate

  • 库 crate
1
2
3
4
5
6
7
8
9
cd hello_world/
# 项目结构
tree .
# .
# ├── Cargo.toml
# └── src
# └── lib.rs
#
# 2 directories, 2 files
  • 二进制 crate
1
2
3
4
5
6
7
8
9
cd hello_world/
# 项目结构
tree .
# .
# ├── Cargo.toml
# └── src
# └── main.rs
#
# 2 directories, 2 files

3.2 包含多个 crate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 创建库crate
cargo new mutil_crate --lib

# 默认可执行文件是 `src/main.rs`,其他可执行文件可以放在 `src/bin/` 中。
# 创建多个二进制crate
mkdir src/bin
vim src/bin/main1.rs
vim src/bin/main2.rs

# Package结构
tree .
.
├── Cargo.toml
└── src
├── bin
│   ├── main1.rs
│   └── main2.rs
└── lib.rs

3 directories, 4 files

# 运行指定二进制crate
cargo run --bin main1
# Compiling mutil_crate v0.1.0 (/home/sec/rust-code/mutil_crate)
# Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.23s
# Running `target/debug/main1`
# Hello, main1!


# 运行库crate单元测试
cargo test --lib
# Compiling mutil_crate v0.1.0 (/home/sec/rust-code/mutil_crate)
# Finished `test` profile [unoptimized + debuginfo] target(s) in 0.27s
# Running unittests src/lib.rs (target/debug/deps/mutil_crate-55531a037166be43)
#
# running 1 test
# test tests::it_works ... ok
#
# test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
#

0x04. 进阶工作空间

工作区是一个或多个包的集合,这些包称为工作区成员,它们一起进行管理。

4.1 功能要点

  • 通用命令可以在所有工作区成员中运行,例如 cargo check --workspace
  • 所有包共享一个位于工作区根目录中的通用 Cargo.lock 文件。
  • 所有包共享一个通用输出目录,该目录默认为工作区根目录中名为 target 的目录。
  • 共享包元数据,例如与 workspace.package
  • Cargo.toml 中的 [patch][replace][profile.*] 部分仅在根清单中被识别,并在成员包的清单中被忽略。

4.2 支持配置

工作区的根 Cargo.toml 支持以下部分:

  • [workspace] — 定义工作区。
    • resolver — 设置要使用的依赖项解析器。
    • members — 工作区中要包含的软件包。
    • exclude — 从工作区中排除的软件包。
    • default-members — 未选择特定软件包时要操作的软件包。
    • package — 软件包中用于继承的键。
    • dependency — 软件包依赖项中用于继承的键。
    • lints — 软件包 lints 中用于继承的键。
    • metadata — 外部工具的额外设置。
  • [patch] — 覆盖依赖项。
  • [replace] — 覆盖依赖项(已弃用)。
  • [profile] — 编译器设置和优化。

4.3 创建工作空间

工作区至少必须有一个成员,要么是根包,要么是虚拟清单。

要创建工作区,请将 [workspace] 表添加到 Cargo.toml

1
2
[workspace]
# ...

4.3.1 有根 Package 的工作空间

如果在已经定义 [package]Cargo.toml 中添加了 [workspace] 部分,则该包是工作区的根包。工作区根目录是工作区的 Cargo.toml 所在的目录。

1
2
3
4
5
6
7
[workspace]

[package]
name = "hello_world" # the name of the package
version = "0.1.0" # the current version, obeying semver
authors = ["Alice <a@example.com>", "Bob <b@example.com>"]

4.3.2 虚拟工作空间

或者,可以创建一个包含 [workspace] 部分但不包含 [package] 部分的 Cargo.toml 文件。这称为虚拟清单。当没有“主”包或您希望将所有包组织在单独的目录中时,这通常很有用。

1
2
3
4
# [PROJECT_DIR]/Cargo.toml
[workspace]
members = ["hello_world"]
resolver = "2"
1
2
3
4
5
6
7
# [PROJECT_DIR]/hello_world/Cargo.toml
[package]
name = "hello_world" # the name of the package
version = "0.1.0" # the current version, obeying semver
edition = "2021" # the edition, will have no effect on a resolver used in the workspace
authors = ["Alice <a@example.com>", "Bob <b@example.com>"]

  • 必须在虚拟工作区中明确设置解析器,因为它们没有 package.edition 来从解析器版本推断它。
  • 默认情况下,在工作区根目录中运行的命令将针对所有工作区成员运行,请参阅 default-members

4.4 小结

  1. 创建工作空间就是在 Cargo.toml 中添加了 [workspace] 部分。
  2. 成员 PackageWorkspace 的数据继承。
  3. cargo 通用命令可以对空间成员进行统一管理。
  4. 具体 [workspace] 部分配置,详情请查看官方指南: Workspaces