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


x-i18n: generated_at: “2026-03-01T14:27:59Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: 5be8eb3df879a8bd6945f165dcc8659dc0d20fb56df108a6bc51a9a2571ebdce source_path: ch14-03-cargo-workspaces.md workflow: 16

Cargo 工作空间 (Cargo Workspaces)

Cargo Workspaces

在第 12 章中,我们构建了一个包含二进制 crate 和库 crate 的包。随着项目的发展,你可能会发现库 crate 会继续变大,你想将包进一步拆分为多个库 crate。Cargo 提供了一个名为“工作空间 (workspaces)”的功能,可以帮助管理并行开发的多个相关包。

In Chapter 12, we built a package that included a binary crate and a library crate. As your project develops, you might find that the library crate continues to get bigger and you want to split your package further into multiple library crates. Cargo offers a feature called workspaces that can help manage multiple related packages that are developed in tandem.

创建工作空间 (Creating a Workspace)

Creating a Workspace

“工作空间 (workspace)”是一组共享同一个 Cargo.lock 和输出目录的包。让我们使用工作空间创建一个项目——我们将使用简单的代码,以便专注于工作空间的结构。构建工作空间有多种方式,这里我们仅展示一种常见方式。我们将拥有一个包含一个二进制文件和两个库的工作空间。二进制文件将提供主要功能,并依赖于这两个库。一个库将提供 add_one 函数,另一个库提供 add_two 函数。这三个 crate 将属于同一个工作空间。我们首先为工作空间创建一个新目录:

A workspace is a set of packages that share the same Cargo.lock and output directory. Let’s make a project using a workspace—we’ll use trivial code so that we can concentrate on the structure of the workspace. There are multiple ways to structure a workspace, so we’ll just show one common way. We’ll have a workspace containing a binary and two libraries. The binary, which will provide the main functionality, will depend on the two libraries. One library will provide an add_one function and the other library an add_two function. These three crates will be part of the same workspace. We’ll start by creating a new directory for the workspace:

$ mkdir add
$ cd add

接下来,在 add 目录中,我们创建用于配置整个工作空间的 Cargo.toml 文件。此文件不会有 [package] 部分。相反,它将以 [workspace] 部分开始,允许我们向工作空间添加成员。我们还决定通过将 resolver 值设置为 "3",在我们的工作空间中使用 Cargo 解析器算法的最新且最伟大的版本:

Next, in the add directory, we create the Cargo.toml file that will configure the entire workspace. This file won’t have a [package] section. Instead, it will start with a [workspace] section that will allow us to add members to the workspace. We also make a point to use the latest and greatest version of Cargo’s resolver algorithm in our workspace by setting the resolver value to "3":

文件名: Cargo.toml

{{#include ../listings/ch14-more-about-cargo/no-listing-01-workspace/add/Cargo.toml}}

接下来,我们通过在 add 目录下运行 cargo new 来创建 adder 二进制 crate:

Next, we’ll create the adder binary crate by running cargo new within the add directory:

$ cargo new adder
     Created binary (application) `adder` package
      Adding `adder` as member of workspace at `file:///projects/add`

在工作空间内运行 cargo new 也会自动将新创建的包添加到工作空间 Cargo.toml[workspace] 定义的 members 键里,如下所示:

Running cargo new inside a workspace also automatically adds the newly created package to the members key in the [workspace] definition in the workspace Cargo.toml, like this:

{{#include ../listings/ch14-more-about-cargo/output-only-01-adder-crate/add/Cargo.toml}}

此时,我们可以通过运行 cargo build 来构建工作空间。你的 add 目录中的文件应该看起来像这样:

At this point, we can build the workspace by running cargo build. The files in your add directory should look like this:

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

工作空间在顶层有一个 target 目录,编译产物将放在其中;adder 包没有自己的 target 目录。即使我们要从 adder 目录内部运行 cargo build ,编译产物最终仍会进入 add/target 而不是 add/adder/target 。Cargo 这样构建工作空间中的 target 目录是因为工作空间中的 crate 旨在相互依赖。如果每个 crate 都有自己的 target 目录,那么每个 crate 都必须重新编译工作空间中的每一个其他 crate,以将产物放入自己的 target 目录中。通过共享一个 target 目录,crate 可以避免不必要的重新构建。

The workspace has one target directory at the top level that the compiled artifacts will be placed into; the adder package doesn’t have its own target directory. Even if we were to run cargo build from inside the adder directory, the compiled artifacts would still end up in add/target rather than add/adder/target. Cargo structures the target directory in a workspace like this because the crates in a workspace are meant to depend on each other. If each crate had its own target directory, each crate would have to recompile each of the other crates in the workspace to place the artifacts in its own target directory. By sharing one target directory, the crates can avoid unnecessary rebuilding.

在工作空间中创建第二个包 (Creating the Second Package in the Workspace)

Creating the Second Package in the Workspace

接下来,让我们在工作空间中创建另一个成员包并将其命名为 add_one 。生成一个名为 add_one 的新库 crate:

Next, let’s create another member package in the workspace and call it add_one. Generate a new library crate named add_one:

$ cargo new add_one --lib
     Created library `add_one` package
      Adding `add_one` as member of workspace at `file:///projects/add`

顶层的 Cargo.toml 现在将在 members 列表中包含 add_one 路径:

The top-level Cargo.toml will now include the add_one path in the members list:

文件名: Cargo.toml

{{#include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml}}

你的 add 目录现在应该包含这些目录和文件:

Your add directory should now have these directories and files:

├── 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 函数:

In the add_one/src/lib.rs file, let’s add an add_one function:

文件名: add_one/src/lib.rs

{{#rustdoc_include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/add_one/src/lib.rs}}

现在我们可以让包含二进制文件的 adder 包依赖于包含库的 add_one 包。首先,我们需要在 adder/Cargo.toml 中添加对 add_one 的路径依赖。

Now we can have the adder package with our binary depend on the add_one package that has our library. First, we’ll need to add a path dependency on add_one to adder/Cargo.toml.

文件名: adder/Cargo.toml

{{#include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/adder/Cargo.toml:6:7}}

Cargo 不会假设工作空间中的 crate 相互依赖,因此我们需要明确依赖关系。

Cargo doesn’t assume that crates in a workspace will depend on each other, so we need to be explicit about the dependency relationships.

接下来,让我们在 adder crate 中使用 add_one 函数(来自 add_one crate)。打开 adder/src/main.rs 文件,修改 main 函数以调用 add_one 函数,如示例 14-7 所示。

Next, let’s use the add_one function (from the add_one crate) in the adder crate. Open the adder/src/main.rs file and change the main function to call the add_one function, as in Listing 14-7.

{{#rustdoc_include ../listings/ch14-more-about-cargo/listing-14-07/add/adder/src/main.rs}}

让我们通过在顶层 add 目录运行 cargo build 来构建工作空间!

Let’s build the workspace by running cargo build in the top-level add directory!

$ 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.22s

要从 add 目录运行二进制 crate,我们可以使用 -p 参数和包名并通过 cargo run 指定我们要运行工作空间中的哪个包:

To run the binary crate from the add directory, we can specify which package in the workspace we want to run by using the -p argument and the package name with cargo run:

$ cargo run -p adder
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/adder`
Hello, world! 10 plus one is 11!

这会运行 adder/src/main.rs 中的代码,它依赖于 add_one crate。

This runs the code in adder/src/main.rs, which depends on the add_one crate.

依赖外部包 (Depending on an External Package)

注意工作空间在顶层只有一个 Cargo.lock 文件,而不是在每个 crate 的目录中都有一个 Cargo.lock 。这确保了所有 crate 都使用所有依赖项的相同版本。如果我们向 adder/Cargo.tomladd_one/Cargo.toml 文件添加 rand 包,Cargo 将把它们都解析为 rand 的一个版本,并记录在那一个 Cargo.lock 中。使工作空间中的所有 crate 使用相同的依赖项意味着 crate 之间始终是兼容的。让我们将 rand crate 添加到 add_one/Cargo.toml 文件的 [dependencies] 部分,以便我们可以在 add_one crate 中使用 rand crate:

Notice that the workspace has only one Cargo.lock file at the top level, rather than having a Cargo.lock in each crate’s directory. This ensures that all crates are using the same version of all dependencies. If we add the rand package to the adder/Cargo.toml and add_one/Cargo.toml files, Cargo will resolve both of those to one version of rand and record that in the one Cargo.lock. Making all crates in the workspace use the same dependencies means the crates will always be compatible with each other. Let’s add the rand crate to the [dependencies] section in the add_one/Cargo.toml file so that we can use the rand crate in the add_one crate:

文件名: add_one/Cargo.toml

{{#include ../listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add/add_one/Cargo.toml:6:7}}

我们现在可以将 use rand; 添加到 add_one/src/lib.rs 文件中,通过在 add 目录中运行 cargo build 构建整个工作空间,将会引入并编译 rand crate。我们将得到一个警告,因为我们没有引用我们引入作用域的 rand

We can now add use rand; to the add_one/src/lib.rs file, and building the whole workspace by running cargo build in the add directory will bring in and compile the rand crate. We will get one warning because we aren’t referring to the rand we brought into scope:

$ cargo build
    Updating crates.io index
  Downloaded rand v0.8.5
   --snip--
   Compiling rand v0.8.5
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
warning: unused import: `rand`
 --> add_one/src/lib.rs:1:5
  |
1 | use rand;
  |     ^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `add_one` (lib) generated 1 warning (run `cargo fix --lib -p add_one` to apply 1 suggestion)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s

顶层的 Cargo.lock 现在包含了关于 add_one 依赖 rand 的信息。然而,即使 rand 在工作空间的某个地方被使用,除非我们也向其他 crate 的 Cargo.toml 文件中添加 rand ,否则我们无法在该工作空间的其他 crate 中使用它。例如,如果我们向 adder 包的 adder/src/main.rs 文件添加 use rand; ,我们将得到一个错误:

The top-level Cargo.lock now contains information about the dependency of add_one on rand. However, even though rand is used somewhere in the workspace, we can’t use it in other crates in the workspace unless we add rand to their Cargo.toml files as well. For example, if we add use rand; to the adder/src/main.rs file for the adder package, we’ll get an error:

$ cargo build
  --snip--
   Compiling adder v0.1.0 (file:///projects/add/adder)
error[E0432]: unresolved import `rand`
 --> adder/src/main.rs:2:5
  |
2 | use rand;
  |     ^^^^ no external crate `rand`

要修复此问题,请编辑 adder 包的 Cargo.toml 文件,并指明 rand 也是它的依赖项。构建 adder 包会将 rand 添加到 Cargo.lockadder 的依赖项列表中,但不会下载额外的 rand 副本。Cargo 将确保工作空间中使用 rand 包的每个包中的每个 crate 都使用相同的版本,只要它们指定了兼容的 rand 版本,这既节省了空间,又确保了工作空间中的 crate 能够彼此兼容。

To fix this, edit the Cargo.toml file for the adder package and indicate that rand is a dependency for it as well. Building the adder package will add rand to the list of dependencies for adder in Cargo.lock, but no additional copies of rand will be downloaded. Cargo will ensure that every crate in every package in the workspace using the rand package will use the same version as long as they specify compatible versions of rand, saving us space and ensuring that the crates in the workspace will be compatible with each other.

如果工作空间中的 crate 指定了相同依赖项的不兼容版本,Cargo 将解析它们中的每一个,但仍会尝试解析尽可能少的版本。

If crates in the workspace specify incompatible versions of the same dependency, Cargo will resolve each of them but will still try to resolve as few versions as possible.

为工作空间添加测试 (Adding a Test to a Workspace)

Adding a Test to a Workspace

作为另一项增强,让我们在 add_one crate 中添加对 add_one::add_one 函数的测试:

For another enhancement, let’s add a test of the add_one::add_one function within the add_one crate:

文件名: add_one/src/lib.rs

{{#rustdoc_include ../listings/ch14-more-about-cargo/no-listing-04-workspace-with-tests/add/add_one/src/lib.rs}}

现在在顶层 add 目录运行 cargo test 。在像这样结构的工作空间中运行 cargo test 会运行工作空间中所有 crate 的测试:

Now run cargo test in the top-level add directory. Running cargo test in a workspace structured like this one will run the tests for all the crates in the workspace:

$ cargo test
   Compiling add_one v0.1.0 (file:///projects/add/add_one)
   Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)

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

     Running unittests src/main.rs (target/debug/deps/adder-3a47283c568d2b6a)

running 0 tests

test result: ok. 0 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

输出的第一部分显示 add_one crate 中的 it_works 测试通过了。下一部分显示在 adder crate 中没有找到测试,接下来的最后一部分显示在 add_one crate 中没有找到文档测试。

The first section of the output shows that the it_works test in the add_one crate passed. The next section shows that zero tests were found in the adder crate, and then the last section shows that zero documentation tests were found in the add_one crate.

我们还可以通过使用 -p 标志并指定我们要测试的 crate 名称,从顶层目录运行工作空间中某个特定 crate 的测试:

We can also run tests for one particular crate in a workspace from the top-level directory by using the -p flag and specifying the name of the crate we want to test:

$ 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-93c49ee75dc46543)

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

此输出显示 cargo test 仅运行了 add_one crate 的测试,而没有运行 adder crate 的测试。

This output shows cargo test only ran the tests for the add_one crate and didn’t run the adder crate tests.

如果你将工作空间中的 crate 发布到 crates.io,工作空间中的每个 crate 都需要分别发布。与 cargo test 类似,我们可以通过使用 -p 标志并指定我们要发布的 crate 名称来发布工作空间中的特定 crate。

If you publish the crates in the workspace to crates.io, each crate in the workspace will need to be published separately. Like cargo test, we can publish a particular crate in our workspace by using the -p flag and specifying the name of the crate we want to publish.

作为额外的练习,尝试以类似于 add_one crate 的方式向此工作空间添加一个 add_two crate!

For additional practice, add an add_two crate to this workspace in a similar way as the add_one crate!

随着你的项目增长,请考虑使用工作空间:它使你能够处理更小、更容易理解的组件,而不是一大坨代码。此外,如果 crate 经常同时更改,将它们放在工作空间中可以使 crate 之间的协调更容易。

As your project grows, consider using a workspace: It enables you to work with smaller, easier-to-understand components than one big blob of code. Furthermore, keeping the crates in a workspace can make coordination between crates easier if they are often changed at the same time.