Cargo 工作区依赖治理:版本统一不是复制粘贴
一、多包项目最怕依赖漂移
Rust 项目从单包变成工作区后,依赖管理会变复杂。多个 crate 各自声明 tokio、serde、anyhow 版本,时间久了就可能出现版本漂移、feature 不一致和编译时间上升。
Cargo 工作区依赖治理的目标,是让公共依赖集中声明,让每个 crate 只声明自己需要什么。版本统一不是复制粘贴,而是减少重复决策。
我之前维护过一个 4 个 crate 的工作区。core 用 tokio 1.30,cli 用 tokio 1.28,plugin 还停在 1.25。三个不同版本同时编译,binary 里带了三份 tokio,体积多了 8MB。更麻烦的是 feature 也不一致:cli 开了full,core 只开了rt-multi-thread。明明是同一套异步运行时,却因为版本不一致,调出来的行为都不一样。
二、依赖可以放到 workspace
flowchart TD A[workspace Cargo.toml] --> B[crate cli] A --> C[crate core] A --> D[crate plugin] B --> E[统一依赖版本]工作区根目录可以声明 workspace dependencies。子 crate 使用 workspace 版本,避免每个包单独写一遍。这样升级 serde 或 tokio 时,只改一个地方。
feature 也要治理。某个 crate 需要 tokio full,另一个只需要 sync,如果全部默认 full,会增加编译和二进制体积。feature 应按实际需要开启。
三、配置示例要清楚
[workspace.dependencies] serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } [dependencies] serde = { workspace = true } tokio = { workspace = true }子 crate 引用 workspace 依赖后,版本由根配置统一管理。这样不会出现 core 用一个版本、cli 用另一个版本的情况。
[features] default = [] ai = ["dep:reqwest"]可选能力用 feature 管理,但不要默认打开所有能力。AI 工具里,网络请求、WASM、向量库客户端都可以按需启用。
生产环境实战经验
用workspace.dependencies最容易被忽略的是版本号的写法。version = "1"表示接受 1.x 任意版本,每次 lock 文件更新都可能拉到新版本。version = "1.30"精确控制到小版本。团队项目建议写精确版本,避免某天 CI 突然挂掉,结果是 tokio 1.31 改了某个行为。但精确版本要定期批量升级,否则半年后还在用旧版本,安全更新也落下了。
四、依赖治理要有检查
可以定期运行cargo tree查看重复依赖和 feature。编译慢、二进制大、依赖冲突,很多都能从依赖树看到线索。
还要注意公共 crate 边界。core crate 不应依赖 CLI 输出库,协议 crate 不应依赖重型运行时。依赖方向清楚,工作区才不会变成一团线。
工作区还要统一 lint 和格式化。根目录配置 clippy、rustfmt 和测试命令,避免每个 crate 风格不同。系统工具链项目通常会越拆越多,早期统一规范很省心。
发布版本也要规划。多个 crate 是一起发版,还是独立发版,取决于 API 边界。如果内部 crate 不对外暴露,可以跟随主包版本;公共协议 crate 则要更谨慎,避免破坏下游。
feature 组合要测试。默认 feature、最小 feature、全部 feature 都可能编译不同代码路径。CI 至少覆盖常用组合,否则某个可选能力可能长期坏掉。
依赖漂移导致的一次线上故障
团队有个插件系统用serde_json序列化通信协议。生产环境插件用的是 workspace 版本 1.0.105,开发环境某次cargo update后自动升到了 1.0.108。新版本对 JSON 某些边界情况的解析策略变了,生产环境的旧版本解析失败。问题出在两个环境 lock 文件不同步。从那以后,CI 每次构建都会对比 lock 文件 hash,任何依赖于版本冲突的 bug 都能第一时间发现。
最后,依赖升级应小步进行。一次升级十几个核心依赖,出问题很难定位。版本治理的核心是可追踪,而不是永远停在旧版本。
日常维护中,一个很实用的命令是cargo tree --duplicates。它能列出哪些 crate 被编译了多个版本。发现重复版本后,可以用cargo tree --invert <crate>反查是谁引用了旧版本。把这个命令放到 CI 里,设定最多允许 0 个重复依赖,版本漂移就无处可藏了。
五、总结
Cargo 工作区依赖治理要使用 workspace dependencies 统一版本,按需开启 feature,并定期检查依赖树。
多包项目的稳定性,来自边界和依赖方向。版本统一不是复制粘贴,而是把重复决策收敛到一个地方。