通过 Rspack 源码学 Rust(零):Cargo workspace 与统一依赖版本

Rust 学习从 Rspack 的仓库结构开始。Rspack 有许多 crate,它们需要共享 Rust 版本、依赖版本和锁文件;Cargo workspace 就是这层组织机制。

1. workspace 是什么

一个 workspace 是多个相关 package 的集合。根目录的 Cargo.toml 描述公共规则,crates/*/Cargo.toml 描述各自职责。每个 crate 仍可以单独声明库或二进制入口,但 Cargo 会把它们放进同一张依赖图。

典型结构:

text 复制代码
rspack/
  Cargo.toml
  Cargo.lock
  crates/
    rspack_core/Cargo.toml
    node_binding/Cargo.toml

workspace 适合大型 Rust 工程,因为 core、绑定和插件可以独立编译、测试和发布,同时共享依赖解析结果。

2. workspace = true 做了什么

Rspack 成员清单中可以看到:

toml 复制代码
edition.workspace = true
version.workspace = true
rspack_core = { workspace = true }

它表示"到 workspace 根清单查找这个字段"。根清单可以集中声明版本:

toml 复制代码
[workspace.package]
edition = "2021"
version = "0.1.0"

[workspace.dependencies]
rspack_core = { path = "crates/rspack_core", version = "0.1.0" }

成员只写 workspace = true,就不会各自重复维护版本字符串和路径。依赖升级时修改根配置,Cargo 重新解析整个 workspace。

3. 统一版本不等于统一 feature

成员可以在继承依赖后追加 feature:

toml 复制代码
napi-derive = { workspace = true, features = ["compat-mode", "type-def"] }

版本来自根清单,feature 来自当前成员。feature 是 Cargo 的编译配置,可能影响条件编译和生成代码,所以每个 crate 仍需要明确自己的需求。

4. feature 到底是什么

Cargo feature 是一个编译期开关。它本质上是一个名字,Cargo 根据启用的名字向编译器传递 cfg(feature = "...") 条件;代码可以据此选择是否编译某些模块、依赖或实现:

toml 复制代码
[features]
browser = []
rust 复制代码
#[cfg(feature = "browser")]
pub fn platform_name() -> &'static str {
  "browser"
}

执行 cargo build --features browser 时,这段函数参与编译;不启用时,函数不会进入当前构建。feature 只影响编译配置,不是运行时变量,也不是环境变量。

Rspack 的 crates/node_binding/Cargo.toml 中有:

toml 复制代码
[features]
browser = ["rspack_binding_api/browser"]
debug_tool = ["rspack_binding_api/debug_tool"]
plugin = ["rspack_binding_api/plugin"]

这里左边是 rspack_node 自己对外提供的开关,右边是依赖 crate 的 feature。用户执行 cargo build -p rspack_node --features browser 时,Cargo 会同时启用 rspack_binding_api 的 browser feature。这个写法叫 feature 转发:上层 crate 暴露一个统一入口,下层 crate 负责真正的实现。

下层的定义更加具体:

toml 复制代码
[features]
browser = ["dep:rspack_browser", "rspack_napi/browser"]
debug_tool = ["rspack_core/debug_tool"]

dep:rspack_browser 表示启用一个 optional dependency;rspack_core/debug_tool 表示继续打开另一个依赖 crate 的 feature。于是一个开关可以沿依赖图向下传播,最终决定哪些代码和依赖被编译。

5. 依赖 feature、crate feature 与 default-features

要区分三种写法:

toml 复制代码
[features]
plugin = ["rspack_util/plugin"]

[dependencies]
napi = { workspace = true, features = ["async", "tokio_rt"] }
swc_core = { workspace = true, default-features = false, features = ["ecma_transforms_react"] }

features 表中的 plugin 是当前 crate 的 feature。它需要用户用 --features plugin 显式打开,并可以转发到其他 crate。

依赖表中的 features = ... 是直接启用依赖 crate 的 feature。上例中 napi 的异步和 Tokio 运行时支持会随当前 crate 一起编译;它不要求用户再单独执行 --features async。

default-features = false 会关闭该依赖默认启用的 feature,然后只保留当前列出的 ecma_transforms_react。这常用于减少编译内容、避免不需要的系统能力,或让 Rspack 明确控制依赖的功能集合。关闭默认 feature 后,依赖可能缺少默认 API,代码和平台配置必须随之匹配。

Rspack 根 Cargo.toml 使用 resolver = "2"。在同一次构建中,依赖 feature 通常按启用请求合并;如果两个 crate 分别要求同一个依赖的不同 feature,最终构建会包含它们的并集。resolver 2 会减少 dev-dependencies、build-dependencies 和部分平台依赖之间不必要的 feature 串联,但它不会把同一个正常依赖强行拆成两份独立 feature 实例。因此 feature 应被设计成可组合的增量能力。

6. 如何选择 feature

可以按下面的顺序判断:

  1. 这是当前 crate 的能力开关吗?写在 features,必要时转发到依赖。
  2. 这是第三方库必须启用的 API 吗?写在依赖项的 features。
  3. 默认能力是否会引入不需要的代码或平台依赖?考虑 default-features = false,再逐项添加需要的 feature。
  4. feature 是否会改变公开 API、依赖数量或目标平台?若会,应该在 crate 文档和构建命令中说明。

feature 适合表达编译期能力组合,例如 Rspack 的浏览器绑定、调试工具和插件支持;不适合保存用户运行时才决定的配置,运行时配置应使用普通参数、环境变量或配置文件。

7. 最小可运行示例

建立一个 workspace:

text 复制代码
demo/
  Cargo.toml
  crates/a/src/lib.rs
  crates/b/src/main.rs

根 Cargo.toml:

toml 复制代码
[workspace]
members = ["crates/a", "crates/b"]
resolver = "2"

[workspace.dependencies]
demo-a = { path = "crates/a" }

crates/b/Cargo.toml:

toml 复制代码
[package]
name = "demo-b"
version.workspace = true
edition.workspace = true

[dependencies]
demo-a = { workspace = true }

执行 cargo run -p demo-b 时,Cargo 会先解析两个成员,再编译 demo-a,最后链接 demo-b。成员名、package 名和目录名可以不同,依赖表中的 package 名才是引用依据。

8. 常见误区

  1. workspace = true 只能继承根 workspace 已声明的字段,不能凭空创建依赖。
  2. workspace 统一解析版本,不代表每个 crate 都启用了相同 feature。
  3. Cargo.lock 位于 workspace 根时,通常由整个工程共享;不要把它理解成单个 crate 的私有配置。
  4. crate 之间仍要保持清晰依赖方向;workspace 不会自动阻止循环设计。

简要说明

从 Rspack 的 Cargo 配置认识 workspace、workspace = true 和依赖继承,理解多 crate 工程如何统一版本并保留局部 feature。

相关推荐
福大大架构师每日一题3 小时前
rust 1.99.1 正式更新:聚焦 UEFI、嵌入式、x86_64 FFI 与 macOS 链接体验的三项修复
开发语言·macos·rust
小道士写程序3 小时前
Rust 元组详解:复合类型的基础
开发语言·rust
小灰灰搞电子15 小时前
Rust+Slint 实现FTP客户端调试助手源码分享
开发语言·后端·rust
Flynt16 小时前
Cloudflare 用 Rust 重写了 TypeScript:我拿同一批代码跑了跑,报错一模一样,退出码不一样
rust·typescript·前端工程化
miofly1 天前
GitHub 今日推荐|pandacode:本地桌面统一管理多个AI编码代理
rust·开源·github
怪我冷i1 天前
Rust 核心语法与内存安全机制
开发语言·安全·rust
程工造Agent2 天前
从 0 到 1 学 Codex 源码:顺着 main 读懂 Rust 闭包与 async move
rust·ai编程
shimly1234562 天前
(done) RUST 指针详解
rust
geovindu2 天前
rust: Template Method Pattern
开发语言·后端·设计模式·rust·行为模式·模板方法模