出于代码可读性的需要,我们倾向于把大而全的代码模块分割成小而精的代码模块。Rust 的包管理工具 cargo 就提供了实现这个特性的功能,即 _workspace_。
什么是 workspace
一个 _workspace_ 其实就是共享同一个 Cargo.lock 以及同一个 target 输出目录的多个 crates 的集合。比如说,在一个项目中,我们分了两个库,一个可执行程序,这个可执行程序依赖于那两个库。那么这里我们就有了三个 crates,然我们把这个三个 crates 以同级的方式放在同一个目录下。那么它们三个就组成了一个_ workspace_。
创建 workspace
创建 _workspace_ 的方式比较简单。比如我们这里就有两个库,一个依赖那两个库的可执行程序,假设这个可执行程序的名称就是 adder,那两个库分别是 add_one, add_two。
首先我们创建一个 add 文件夹,并进入到该目录下,然后创建一个 Cargo.toml 文件,如下:
mkdir addcd addtouch Cargo.toml
然后在 _add/Cargo.toml_ 文件中添加如下内容:
[workspace]members = ["adder","add_one","add_two",]
可以看到,该 Cargo.toml 文件和普通单独 crates 模块中的 Cargo.toml 文件有所不同,它不需要 【package】以及其它的元数据,直接就是以 【workspace】这个段开始。
比如我们在该 workspace 顶层的 Cargo.toml 中添加一个 [dependencies] 段,则使用 cargo 编译的时候,就会遇到如下的错误:
PS D:\Projects\Rust\add> cargo runerror: failed to parse manifest at `D:\Projects\Rust\add\Cargo.toml`Caused by:this virtual manifest specifies a [dependencies] section, which is not allowed
错误提示你,这是一个虚清单文件,不被允许添加 [dependencies] 段。
然后我们分别在 add 文件夹下 使用 cargo new 命令创建 adder,add_one,add_two 三个 crates模块。如下:
cargo new --bin addercargo new --lib add_onecargo new --lib add_two
做好上面的设置之后,add 文件的目录结构看起来应该如下所示:
├── Cargo.lock├── Cargo.toml├── add_one│ ├── Cargo.toml│ └── src│ └── lib.rs├── add_two│ ├── Cargo.toml│ └── src│ └── lib.rs├── adder│ ├── Cargo.toml│ └── src│ └── main.rs└── target
设置 workspace 中各 crates 的依赖
假设 add_one 中提供了一个函数 add_one(i32)->32 函数,add_two 中提供了一个函数 add_two(i32)->i32 函数。因为 Cargo 并不会假设 _workspace_ 中的 crates 相互之间有依赖,所以如果我们需要在 adder 中需要调用这两个函数,那么我们需要在 adder 的 Cargo.toml 中手动添加依赖,如下:
[dependencies]add_one = { path = "../add_one" }add_two = { path = "../add_two" }
然后我们在 add/adder/src/main.rs 中进行如下调用:
use add_one::add_one;use add_two::add_two;fn main() {println!("call add_one for 12: {}", add_one(12));println!("call add_two for 100: {}", add_two(100));}
workspace 中依赖的外部包
注意,我们前面说了,同一个 _workspace_ 在 workspace 顶层目录是共享同一个 Cargo.lock 的,所以,这就保证了一个 _workspace_ 下的所有的 crates 对于所有的依赖都具有相同的版本,比如,add_one 需要依赖一个外部包 rand,add_two 也需要依赖一个外部包 rand,且它们依赖 rand 的版本不同,cargo 会解析对应的 toml 文件并保留其中一个版本后写到 _workspace_ 的 Cargo.lock 文件中。
注意,对于外部依赖,每个用到的 crate 都需要在自己的 toml 文件中显式写明,并没有一个公共的地方写一次之后整个 _workspace_ 内的 crates 都可以直接使用了。这样的好处就是各个 crates 都是真正独立的,单独拿出来都是可以正常编译使用的。
workspace 下的运行测试
即使是同一个 _workspace_ 中,也有可能里面有多个可执行程序需要单独编译,单独运行,比如在有多个可执行程序的 _workspace_ 中执行 cargo run 命令的时候,cargo 就会提示你,它无法确定你要运行的时候哪一个 bin 文件,需要你手动执行 --bin 选项指定或是在 toml 文件中以 default-run 的方式指定默认运行的程序。如下:
PS D:\Projects\Rust\add> cargo runerror: `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key.available binaries: adder, multiPS D:\Projects\Rust\add> cargo run --bin adderwarning: D:\Projects\Rust\add\Cargo.toml: unused manifest key: workspace.default-runFinished dev [unoptimized + debuginfo] target(s) in 0.03sRunning `target\debug\adder.exe`call add_one for 12: 13call add_two for 100: 102
其实你使用 -p 选项也是可以的,如下:
PS D:\Projects\Rust\add> cargo run -p adderwarning: D:\Projects\Rust\add\Cargo.toml: unused manifest key: workspace.default-runFinished dev [unoptimized + debuginfo] target(s) in 0.03sRunning `target\debug\adder.exe`call add_one for 12: 13call add_two for 100: 102
同样的,对于各 crates 的单元测试也是一样,简单的执行 cargo test 命令,会运行 _workspace_ 下的所有单元测试,如下:
使用 cargo test -p add_one 则只会运行 add_one 下的单元测试。如下:
总结
可以看到,_workspace_ 对于分割大代码,管理各个crates提供了很方便的工具,使得同一个项目的大量代码可以通过分割成各个小模块,分别独立管理,也易于理解。
subsrate 代码就是利用 _workspace_ 管理的一个很大的项目。使得整个代码的目录结构清晰明了。
