Skip to content

哈喽,大家好呀,我是呼噜噜,还在被Rust编译时间折磨?依赖管理让你头疼?今天我们来聊聊Rust中能让你的开发效率飙升的神器——工作区Workspace

在Rust中,crate是一个编译单元,是Rust编译器一次性考虑的最小代码量,crate可以是可执行crate(bin)也可以是库crate(lib)

当我们项目逐渐庞大,难以维护或者cargo打包时间过长时,可以将其拆分为多个更小、更易于管理的组件。工作区Workspace就是一种在同一Cargo.toml 根目录下管理多个 crate的机制

它允许你将多个相关的crate结合起来,协同工作、共享依赖关系,以及更方便地进行管理和构建。这些crate共享同一套依赖缓存、Cargo.lock 文件和target 输出目录。Workspace有点类似Java中的Maven父项目与子模块

使用wordspace的优势

那我们使用wordspace有哪些优势:

  1. 统一依赖管理,避免版本地狱
  2. 提升编译速度
  3. 代码复用、模块化项目
  4. 共享构建产物(target 目录)
  5. 支持多二进制或多库统一管理

统一依赖管理,避免版本地狱

  • 所有子crate 共享一个**根目录的 **Cargo.lock
  • 当依赖更新时,不需要每个 crate 单独维护版本
  • 避免了版本冲突和重复下载编译

比如我们在根Cargo.toml里设置,serdetokio的依赖:

plain
[workspace.dependencies]
serde = "1.0"
tokio = { version = "1.36", features = ["full"] }

然后在任意子crateCargo.toml中,就可以如下直接引用:

plain
[dependencies]
serde = { workspace = true }
tokio = { workspace = true }

这样就保证了,所有子crateserdetokio的依赖都和根目录一致,后续升级依赖的版本时,只需改一个地方,全项目生效

提升编译速度

cargo管理rust项目确实很方便,但为了换取极高性能和极高安全性的运行时,导致cargo在编译时存在编译的中间产物过大,与编译速度过慢的代价

将一个中大型项目的crate,拆分成多个小cratecargo可以识别,每次编译时,哪些crate被修改:

  • 未修改的子模块会被 缓存
  • 只重新编译受影响的 crate;
  • 并行构建多个 crate,提高编译效率。

这样使用workspace可以节省30%~70%的编译时间

代码复用、模块化项目

使用workspace,可以将公共逻辑抽到独立 crate,比如 coreutils。然后各业务模块(如 servicecliweb)只需引用这些 crate

这样可以使得项目结构清晰,高内聚,低耦合

我们来看一个例子:

plain
my-app/
├── Cargo.toml          # workspace root
├── common/               # 公共逻辑(domain、utils)
│   └── Cargo.toml
├── api/                # Web 接口层(axum、warp等)
│   └── Cargo.toml
└── cli/                # 命令行工具(reqwest、tokio)
    └── Cargo.toml

项目结构如上,api 依赖 commoncli 依赖 common,但它们相互独立,避免循环依赖

共享构建产物(target 目录)

所有 crate 默认使用 同一个 target目录,这样可以避免重复编译相同依赖,节省磁盘空间和时间

如果你单独编译 apicli,是不会重复构建common依赖的,这样编译速度更快

一次性构建/测试

无论你在 Workspace 的哪个目录(根目录、my_cli 里、my_lib 里都一样),执行下面的命令,都可以一次性依此测试 Workspace 中的所有成员

plain
cargo test --workspace

如果你仅执行cargo test,仅会测试在 Workspace 中当前目录的成员。当然如果你在根目录执行,那么也会一次性测试Workspace中的所有成员。cargo build也是类似cargo test

支持多二进制或多库统一管理

一个 workspace 可以包含多个可执行程序,比如同时提供一个 CLI 工具、一个后台服务、一个 Web API等

示例:

plain
workspace/
 ├─ web/
 ├─ cli/
 └─ lib/

按照下面命令就可指定服务运行:

plain
cargo run -p cli
cargo run -p web

接下来我们具体实操一下,如何创建和使用工作区

创建项目

先创建一个空项目

plain
mkdir my_rust_workspace
cd my_rust_workspace
cargo init

该命令执行完成后,我们会在当前目录下生成一个名为my_rust_workspace的项目,并项目中生成一个Cargo.toml配置文件,我们将这个文件所在的目录称为my_rust_workspace这个项目的根目录

根目录的 Cargo.toml 本身不是一个 crate,它只负责管理其他 crate 成员,所以我们删除根目录下的src文件夹

修改根目录下的cargo.toml

bash
# [package] 这个字段表明着根目录包含的内容是一个package包,那么这个目录需要符合rust的package结构,包含src/main.rs
# name = "my_rust_workspace"
# version = "0.1.0"
# edition = "2024"

# [dependencies]

[workspace]
# "members" 列表告诉 cargo 在哪里寻找我们的 crate
members = [
    "my_lib",
    "my_app",
]

#在 [workspace.dependencies] 中定义共享依赖
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.48", features = ["full"] }

接着我们来创建2个Crate,在根目录执行如下命令:

  1. 创建 my_lib (库):
bash
cargo new my_lib --lib
  1. 创建 my_app (可执行文件):
bash
cargo new my_app //等同于cargo new my_app --bin

我们cargo build一下,看有没有报错,没有就ok

设置 Crate 间的依赖关系

在项目中一般是my_app 依赖 my_lib

  1. 我们先编辑my_lib,在这个crate中src下创建lib.rs:
bash
pub fn greet(name: &str) -> String {
    format!("Hello, {}! Welcome to my workspace.", name)
}

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

    #[test]
    fn it_works() {
        assert_eq!(greet("Rust"), "Hello, Rust! Welcome to my workspace.");
    }
}
  1. 编辑 my_libCargo.toml
bash
[package]
name = "my_lib"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { workspace = true } #新增

这里假设我们的库需要 serde,我们可以直接使用workspace = true 来“继承”根 Cargo.toml 中定义的 serde 版本

  1. 编辑 my_appCargo.toml
xml
[package]
name = "my_app"
version = "0.1.0"
edition = "2024"

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

tokio = { workspace = true }

注意my_lib = { path = "../my_lib" }这样设置的话,my_app就会依赖my_lib这个crate

  1. my_app中调用my_lib 中的函数

修改my_app中的main.rs:

rust
use my_lib::greet;
fn main() {
    let message = greet("Rust Developer");
    println!("{}", message);
}
  1. 测试

在根目录下执行一下命令:

xml
#cargo test

running 0 tests

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

     Running unittests src\lib.rs (target\debug\deps\my_lib-25fa19cda2c6dd82.exe)

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 my_lib

running 0 tests

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

Hello, Rust Developer! Welcome to my workspace.

整个项目测试通过.

可以嵌套Workspace吗?

不可以。Cargo明确不支持嵌套的 Workspace。如果一个 Cargo.toml 定义了 [workspace],那么它的子目录中就不能再有另一个 Cargo.toml 也定义 [workspace]

尾语

如果你的Rust项目正在变得臃肿,编译时间越来越长,现在是时候尝试Workspace了! 它会让你的Rust开发体验提升一个档次。

觉得有用的话,欢迎关注我的公众号,后续会带来更多Rust实战技巧!

本篇文章的完整版demo,扫码到公众号中,回复关键字:my_rust_workspace,即可获取下载链接

作者:小牛呼噜噜

本文到这里就结束啦,感谢阅读,关注同名公众号:小牛呼噜噜,防失联+获取更多技术干货