如何检测重复依赖?
解读
在国内 Rust 工程化面试中,“重复依赖”不仅指同一个 crate 被不同版本多次引入,还包括同一版本被不同路径(git、registry、local)重复编译,以及 feature 组合爆炸导致的编译时重复。面试官想确认候选人能否:
- 快速定位问题;
- 给出可落地的排查与治理方案;
- 兼顾国内网络环境(registry 镜像、git 慢)与 CI 成本。
知识点
- Cargo 依赖解析算法:最近共同祖先原则,同一 crate 只允许一个版本,除非出现“私有依赖”或“不同 major 版本”。
- cargo tree:官方工具,支持重复依赖(
-d)与特征(-e features)双重过滤。 - cargo metadata:输出 JSON,可编程化分析重复节点,国内二次开发脚本常用。
- cargo-deny:Rust 安全组维护,支持bans.duplicate规则,可在 CI 阶段强制拒绝重复依赖。
- resolver = "2":Rust 1.51 引入,解决旧解析器因 feature unification 导致的隐式重复。
- 国内镜像源:crates-io 代理(如 tuna、ustc)可能缓存延迟,导致本地与 CI 看到的重复集合不一致,需锁定Cargo.lock并校验 checksum。
答案
步骤一:本地快速扫描
cargo tree -d --format "{p} {f}"
-d 只打印重复节点,配合 --format 可直观看到版本与特征差异。
步骤二:锁定并保存证据
cargo metadata --format-version 1 | jq '.. | .dependencies? // empty | .[] | select(.duplicate)' > dup.json
将 dup.json 纳入代码库,方便 CR 与后续回归。
步骤三:引入 cargo-deny 做门禁
在 deny.toml 中写入:
[bans]
multiple-versions = "deny"
wildcards = "deny"
CI 阶段执行 cargo deny check bans,失败即阻断合并,保证主干分支零重复。
步骤四:治理策略
- 若重复来自不同 major 版本,评估升级成本,优先统一到最新大版本;
- 若因私有依赖引入,可在中间 crate 使用
pub use重导出,消除下游直接依赖; - 对git 与 registry 混用场景,统一成 registry 版本,避免源码重复克隆;
- 开启
resolver = "2",防止 feature 组合爆炸带来的隐式重复; - 定期运行
cargo audit与cargo deny,形成周检节奏,防止重复回潮。
拓展思考
- 巨型单体仓库(如 ToB 交付项目)中,重复依赖可能隐藏在深层子 crate。可编写cargo 插件递归调用
cargo metadata,生成依赖热力图,按编译时长排序,优先治理耗时最大的重复节点。 - 国内客户常要求离线编译,此时需把重复依赖的源码也打入 vendor。利用
cargo vendor --versioned-dirs可保留版本子目录,避免同名覆盖;随后写脚本比对vendor/*/Cargo.toml的package.version,输出重复清单,作为交付文档的一部分。 - 在嵌入式 no_std 场景,重复依赖会直接导致 Flash 双倍占用。可结合
cargo bloat --crates与cargo tree -d,找出既重复又体积大的 crate,用特征裁剪或fork 精简方式瘦身,实现“零成本去重”。 - 面试时可反问面试官:贵司 CI 是否已接入 cargo-deny?若未接入,可主动提出MR 模板+pre-push hook 双保险方案,展示工程落地思维,拉高面试评分。