用 cargo tree 的 -i 反向查一个 trait 被谁引入

前阵子在一个 1.78 的 workspace 里遇到个怪事:某个小 crate 单独编译没问题,放进 workspace 就报 trait 方法不存在,错误指向的是 serde::de::Deserialize,但代码里根本没直接写这个路径。整个 workspace 大约 40 个成员,依赖树深到 cargo build 输出里滚了快两屏。

我的做法是从报错的类型名反查。先拿到具体是哪个类型没实现 trait,然后用 -i(invert)查这个 crate 是谁引进来的:

cargo tree -i serde -e normal --workspace

-e normal 是为了把 build-deps 和 dev-deps 先滤掉,不然输出里会混进 proc-macro 的依赖,看着非常乱。结果里出现了一个我没印象的中间 crate,版本被锁在 serde 1.0.150 左右,而 workspace 期望的是 1.0.190+。顺着名字去找,是某个内部工具 crate 里的 features = ["derive"] 写错了位置——实际写在了 [dependencies.serde] 下面,但那个 crate 自己又通过 serde_json 拉了一份默认 features 的 serde,两个版本在 feature 合并时表现不一致,trait 的某个默认方法就没进来。

修法是把版本统一:

[workspace.dependencies]
serde = { version = "1.0.190", features = ["derive"] }

然后各成员写 serde.workspace = true。这一步的代价是要改十几处 Cargo.toml,但换来的是一处升级全员生效,不用再猜哪层漏了 feature。

为什么是这个思路:Cargo 的 feature 是并集合并,同一个 crate 的多个版本之间 feature 不共享,所以“明明写了 derive”和“报错说没实现”可以同时成立。cargo tree -i 直接告诉你谁把版本带进来的,比从顶层往下翻快得多;-e normal 的过滤则是为了少看噪音,代价是你可能漏掉 dev-dependency 里的同名 crate,如果问题只在测试里出现,就得去掉这个参数再查一遍。

哪里会失效:-i 只能按包名反查,遇到同名的多个来源(比如内部 crate 和 crates.io 同名)时会给出合并后的树,得配合 --prefix depth 看层级;另外 workspace 里如果用了 [patch] 覆盖,cargo tree 显示的是 patch 后的结果,和 lock 文件里实际的不一定一致,我就踩过一次——patch 的路径写错,树里看着正常,编译时才炸。还有,-e normal 在 miri 或 cargo test --all-features 场景下会滤掉真正的问题来源。

你们 workspace 里遇到版本和 feature 对不上时,是先看 cargo tree 还是先看 lock 文件?我目前还是先看树,但 lock 里的 checksum 和 source 有时更能说明问题。

0
赞同度 100% · 0 条评论 · 6 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。