如何检测重复依赖?

解读

在国内 Rust 工程化面试中,“重复依赖”不仅指同一个 crate 被不同版本多次引入,还包括同一版本被不同路径(git、registry、local)重复编译,以及 feature 组合爆炸导致的编译时重复。面试官想确认候选人能否:

  1. 快速定位问题;
  2. 给出可落地的排查与治理方案;
  3. 兼顾国内网络环境(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,失败即阻断合并,保证主干分支零重复

步骤四:治理策略

  1. 若重复来自不同 major 版本,评估升级成本,优先统一到最新大版本;
  2. 若因私有依赖引入,可在中间 crate 使用 pub use 重导出,消除下游直接依赖;
  3. git 与 registry 混用场景,统一成 registry 版本,避免源码重复克隆;
  4. 开启 resolver = "2",防止 feature 组合爆炸带来的隐式重复
  5. 定期运行 cargo auditcargo deny,形成周检节奏,防止重复回潮。

拓展思考

  1. 巨型单体仓库(如 ToB 交付项目)中,重复依赖可能隐藏在深层子 crate。可编写cargo 插件递归调用 cargo metadata,生成依赖热力图,按编译时长排序,优先治理耗时最大的重复节点。
  2. 国内客户常要求离线编译,此时需把重复依赖的源码也打入 vendor。利用 cargo vendor --versioned-dirs 可保留版本子目录,避免同名覆盖;随后写脚本比对 vendor/*/Cargo.tomlpackage.version,输出重复清单,作为交付文档的一部分。
  3. 嵌入式 no_std 场景,重复依赖会直接导致 Flash 双倍占用。可结合 cargo bloat --cratescargo tree -d,找出既重复又体积大的 crate,用特征裁剪fork 精简方式瘦身,实现“零成本去重”。
  4. 面试时可反问面试官:贵司 CI 是否已接入 cargo-deny?若未接入,可主动提出MR 模板+pre-push hook 双保险方案,展示工程落地思维,拉高面试评分。