Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

14.5 cargo 工作空间 (Workspace)

14.5.1 为什么需要 Cargo Workspace

假如说我们构建了一个二进制 crate,里面既有库又有应用程序。随着项目规模不断增长,库 crate 可能不断变大。在这种情况下通常会把它拆为多个包。针对这种需求,Rust 提供了 Cargo 工作空间,也就是 cargo workspace。

Cargo workspace 会帮助管理多个相互关联且需要协同开发的 crate。其本质是一套共享同一个 Cargo.lock 和输出文件的包。

14.5.2 使用 Workspace

有多种方式可以创建工作空间(workspace)。

做一个例子,这个工作空间里有 1 个二进制 crate 和 1 个库 crate:

  • 二进制 crate 里有 main 函数,依赖于库 crate。
  • 其中一个库 crate 提供一个叫 add_one 的函数。

1. 创建 Workspace 目录

首先为工作空间创建一个目录,我取名叫 add。在终端输入以下命令:

$ mkdir add
$ cd add

2. 在主项目中使用 Workspace

接下来,在 add 目录中,创建将配置整个工作区的 Cargo.toml 文件。该文件不会有 [package] 部分。相反,它将以 [workspace] 部分开头:

[workspace]
resolver = "2"

members = [
    "adder",
]

adder 就是我给二进制 crate 取的名,这个列表可以继续添加更多成员。

3. 添加库

$ cargo new adder
    Creating binary (application) `adder` package

通过这个命令创建了 adder crate,位于目录 add/adder 下。

此时整个项目的结构如下:

├── Cargo.lock
├── Cargo.toml
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

需要注意的是,这时候我们既可以对 add 这个项目使用 cargo build,也可以对 add 下的 adder crate 使用 cargo build。但是生成的 target 目录和 Cargo.lock 文件只会有一个,都在 add 目录下,而 adder crate 的构建产出物也会存放在这里。因为工作空间中的各个 crate 往往是相互依赖的,每个目录都有自己的 target 就会导致开发者不得不反复编译工作空间里的其余 crate。

接下来添加其它 crate:

另一个 crate 叫 add_one,修改工作空间信息:

[workspace]
resolver = "2"

members = [
    "adder",
    "add_one",
]

使用 cargo new 添加库,记得使用 --lib 标志来把它声明为 library crate:

$ cargo new add_one --lib
    Creating library `add_one` package

现在整个项目的结构是:

├── Cargo.lock
├── Cargo.toml
├── add_one
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

4. 编写代码

add_one/src/lib.rs 文件中,添加一个 add_one 函数和一个简单的单元测试:

#![allow(unused)]
fn main() {
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));
    }
}
}

现在我们可以让 adder 包和我们的二进制文件依赖于 add_one。首先,需要添加路径依赖 add_oneadder/Cargo.toml,因为 Cargo 并不假设工作区中的 crate 会相互依赖,因此我们需要明确依赖关系。在 adder/Cargo.toml 中这么写:

[dependencies]
add_one = { path = "../add_one" }

接下来,让我们使用来自 add_one crate 的 add_one 函数。打开 adder/src/main.rs,在顶部添加 use 来把 add_one 引入作用域,然后修改 main 函数来调用 add_one 函数。

use add_one;

fn main() {
    let num = 10;
    println!("Hello, world! {num} plus one is {}!", add_one::add_one(num));
}

5. 编译

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` profile [unoptimized + debuginfo] target(s) in 0.11s

没有报错,正常运行。

6. 测试

我们还可以通过使用 -p 标志并指定我们要测试的包的名称,从顶级目录中对工作区中的一个特定包运行测试。比如说仅测试 add_one 函数:

$ cargo test -p add_one
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/lib.rs (target/debug/deps/add_one-bd89fda78e7f92a7)

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 add_one

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

如果将工作区中的 crate 发布到 crates.io,则工作区中的每个 crate 都需要单独发布。与 cargo test 一样,我们可以使用 -p 指定工作区中要发布的特定包,并写出想要发布的包名称。