本文是对 Rust 官方 Inside Rust 博客《This Development-cycle in Cargo: 1.93》的完整中文解读,涵盖本轮(约 6 周合并窗口期)Cargo 团队在诊断渲染、Lint 系统、构建目录布局、目标目录锁定、结构化日志、配置文件 include、发布时间戳等方向的全部进展。
内容结构概览
- 本轮推荐插件:cargo-override
- 诊断渲染升级:迁移至 annotate-snippets + Unicode 渲染器
- 诊断质量改进:多位贡献者的错误信息优化
- Lint 系统:新增
implicit_minimum_version_req,以及继承依赖的 lint 策略讨论 - 全量警告转错误(
build.warnings):hard warning 的处理与 Rust bootstrap 集成 - Shell 补全:自动补全系统持续推进
- 构建目录布局(build-dir layout):测试阶段与目录命名最终确定
- 自定义最终产物(custom final artifacts):构建脚本产物管理机制的设计探讨
- 目标目录锁定(target dir locking):细粒度锁方案的技术挑战
- 结构化日志(structured logging):新增
cargo report timings - 配置文件 include(
-Zconfig-include):正式稳定化 - pubtime(发布时间戳):依赖解析的时间旅行功能
- 杂项改进
- 暂无进展的关注领域
- 如何参与贡献
一、本轮推荐插件:cargo-override
Cargo 无法满足所有人的全部需求,这部分是由于它必须维护的兼容性保证所决定的。插件在 Cargo 生态中扮演着重要角色,Cargo 团队希望通过定期推荐来为插件作者提供更多曝光。
本轮推荐的插件是 cargo-override,它可以帮助管理 Cargo.toml 中的 [patch] 表,大幅简化依赖覆盖的操作流程。感谢 eopb 的推荐。
二、诊断渲染升级:迁移至 annotate-snippets + Unicode 渲染器
持续迁移至 annotate-snippets
这项工作延续自 Cargo 1.92 周期。jneem 在 #16143 中继续推进将 Cargo 的诊断输出迁移到 annotate-snippets 渲染库的工作,整体进度在 #15944 中跟踪。
rustc 正式采用 annotate-snippets
与此同时,编译器团队提交了一个主要变更提案(MCP),将 nightly rustc 切换到使用 annotate-snippets(compiler-team#937)。该实验进展顺利,已获批准在 rustc 中正式推广使用(compiler-team#947)。
这对 Cargo 的意义在于:它降低了为所有诊断启用 Unicode 渲染器的门槛。Muscraft 随即在 Cargo 中添加了 -Zrustc-unicode 标志(#16243)。
效果对比
启用前:
warning: used import from `std` instead of `core`
--> src/error.rs:25:5
|
25 | use std::num::NonZeroUsize;
| ^^^ help: consider importing the item from `core`: `core`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#std_instead_of_core
note: the lint level is defined here
--> src/lib.rs:53:9
|
53 | #![warn(clippy::std_instead_of_core)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
启用后(Unicode 渲染器):
warning: used import from `std` instead of `core`
╭▸ src/error.rs:25:5
│
25 │ use std::num::NonZeroUsize;
│ ━━━ help: consider importing the item from `core`: `core`
│
╰ help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core
note: the lint level is defined here
╭▸ src/lib.rs:53:9
│
53 │ #![warn(clippy::std_instead_of_core)]
╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━
视觉效果明显更清晰,箭头和分隔符更具层次感。
如何试用
在 ~/.cargo/config.toml 中添加以下配置即可在 nightly 工具链上选择性启用:
toml
[unstable]
rustc-unicode = true
稳定化进度在 rust#148607 中跟踪。
三、诊断质量改进
这也是 1.92 周期的延续工作。本轮有多位贡献者参与了 Cargo 错误消息质量的改进,在此一并致谢:
- 0xPoe(#16125)
- Amberley-Sz(#16241)
- epage(#16216、#16225、#16227、#16233、#16256)
- motorailgun(#16207、#16268)
- TanmayArya-1p(#16338)
四、Lint 系统:新增 implicit_minimum_version_req
背景
这项工作延续自 Cargo 1.90 周期的全员大会(All Hands)讨论。
weihanglo 在 #16321 中添加了新的 lint implicit_minimum_version_req,并在此过程中对 lint 系统本身做了若干改进(#16320、#16324、#16364)。
核心设计问题:在哪里触发 lint?
这一 PR 引发了一个重要讨论:当依赖在 workspace.dependencies 中定义,并被各成员包通过 dep.workspace = true 继承时,lint 应该在 workspace 层触发,还是在每个继承它的包中触发?
场景示例:
根目录 ./Cargo.toml:
toml
[workspace]
members = ["a"]
[workspace.dependencies]
clap = "4"
成员包 ./a/Cargo.toml:
toml
[package]
name = "a"
[dependencies]
clap.workspace = true
方案一:每次继承都触发 lint(类宏语义)
[WARNING] dependency version requirement lacks full precision
--> Cargo.toml:7:7
|
7 | dep = "1"
| ^^^
|
[NOTE] dependency `dep` was inherited
--> a/Cargo.toml:8:5
|
8 | dep.workspace = true
| ----------------
= [NOTE] `cargo::imprecise_version_requirements` is set to `warn` in `[lints]`
方案二:只在定义处触发一次 lint(类函数语义)
[WARNING] dependency version requirement without an explicit minimum version
--> Cargo.toml:7:7
|
7 | dep = "1"
| ^^^ missing full version components
|
= [NOTE] `cargo::implicit_minimum_version_req` is set to `warn` in `[lints]`
类比 Rust 语言本身
这类似于 Rust 代码中宏与函数的区别:
- 宏:对每次调用位置都发出 lint,lint 级别由调用者控制。
- 函数:lint 只在定义处触发,不受调用者影响,有自己的 lint 级别。
衍生问题
lint 级别由谁控制?
在对 workspace.dependencies 进行 lint 时,应使用 workspace.lints 而非各成员包的 lints。Cargo 团队已有对"workspace 级别 lint"的需求(例如虚拟 workspace 的 lint 支持 #13723,以及依赖树的 lint #9930),因此这不是新问题。当前思路是用 workspace.lints 同时承担继承 lint 和控制 workspace lint 的职责。对于隐式 workspace(没有显式 workspace 根的场景),可能需要像 package.resolver 那样做降级回退。
edition 如何确定?
workspace 条目适用哪个 edition?在之前处理依赖继承问题时,团队曾将问题聚焦于"继承行为本身",并将责任归属于包级别,这在那种场景下运作良好,但不适用于所有情形。团队已讨论过引入 workspace 级别 edition 的需求。好在这个问题可以暂时搁置,直到真正需要跨 edition 进行修改时再处理。
最终决定
决定在 workspace.dependencies 条目处触发 lint,但由于该特性目前仍处于不稳定阶段,未来仍有灵活调整的空间。
作为后续跟进,weihanglo 在 #16367 中更新了 unknown_lints lint,使其与 implicit_minimum_version_req 的处理方式保持一致。
五、全量警告转错误(build.warnings)
背景
这项工作延续自 Cargo 1.81 周期。
jyn514 在 Zulip 上讨论了如何改进 Rust bootstrap 构建流程,使其在拒绝或允许警告时无需触发完整重新构建。不稳定特性 build.warnings 似乎正是为此而设。
hard warning 问题
jyn514 尝试在 #148332 中将该特性应用于 bootstrap 时,遇到了 hard warning 相关问题(#14802-comment)。
所谓"hard warning",是指那些在任何情况下都会作为警告发出的诊断,无法被 -Dwarnings 之类的标志抑制。最终团队决定忽略 hard warning,由 epage 在 #16213 中实现。
构建中断时机的讨论
jyn514 还在 Zulip 上指出,目前的实现与 RUSTFLAGS=-Dwarnings 不同:后者会在第一个出现警告的包后立即中止构建,而 build.warnings 当前的行为不会这样做。
epage 建议让这一行为与 --keep-going 标志挂钩:使用 --keep-going 时显示所有警告,否则在第一批警告出现后报错并停止。该提议目前尚未进一步讨论。
六、Shell 补全
这项工作延续自 Cargo 1.83 周期。本轮有多位贡献者推进了新的自动补全系统(#14520):
七、构建目录布局(build-dir layout)
背景
这项工作延续自 Cargo 1.92 周期。
epage 在 #15010 上提出了过渡计划,当前阶段的核心工作是测试,包括:
- crater 大规模兼容性测试(rust#149852)
- Cargo 自身测试套件的调整(#16375)
- 手动测试
测试过程中发现了若干问题,已陆续修复(#16300、#16335、#16348)。
审计与目录命名最终确定
epage 对该特性进行了全面审计,梳理了所有可能影响测试的潜在修改。审计结果包括:
下一步将处理测试结果,并通过"Call for Testing"邀请更广泛的社区参与测试。
八、自定义最终产物(custom final artifacts)
问题背景
构建目录布局属于 Cargo 的内部实现细节,但确实有一些合理的用例需要依赖它。为了便于未来迁移,Cargo 团队希望为那些当前依赖该布局的用户提供替代方案。
其中一个典型用例是:访问构建脚本(build script)生成的最终产物(#13663)。
设计约束
在考虑解决方案时,需要牢记以下几点:
- 依赖树中的任何构建脚本都有可能生成此类产物
- Cargo 会主动避免中间产物和最终产物之间的路径冲突(参见 #6313)
- Cargo 需要在多个
cargo实例并发运行,以及单次cargo调用中多个构建脚本并发运行的情况下保证安全性
方案分析
方案一:直接给构建脚本访问 artifact-dir 的权限
这允许任何构建脚本参与产物输出,但 Cargo 无法检测路径冲突,并发访问时也不安全,因此不可行。
方案二:为每个构建脚本提供独立的暂存目录
构建脚本将产物放入自己的暂存目录,Cargo 负责汇总、检查冲突,再统一复制到 artifact-dir。缺点在于:用户难以预测和解决冲突(需要追踪整个依赖树中所有构建脚本的行为),且如果构建脚本直接生成到暂存目录后产物未被使用,则存在不必要的复制开销。
方案三(当前倾向方案):新增构建脚本指令
在讨论扩展构建脚本与上游包的通信能力(#3544)时,出现了一个新思路:引入一种新的构建脚本指令,让构建脚本能够声明一个产物------既指定产物来源,也指定它相对于 artifact-dir 的目标路径。
这些产物只会对被选中的包进行"uplift"(提升到 artifact-dir),规则与现有产物相同。构建脚本可以读取依赖包构建脚本输出的 cargo::metadata,并据此生成产物指令。
对应设计约束的评估:
- 任何构建脚本都可以生成最终产物,尽管需要一些手动操作
- Cargo 可以在构建脚本指令层面检测冲突并报错。由于所有涉及的构建脚本都由用户所有,用户对冲突具有完整的可见性和自主处理权。唯一的不足是报错发生在构建末尾
- Cargo 负责执行 uplift 操作,可以确保访问的路径处于锁定状态,并发安全
与 RFC #3035 的关系
这一机制可以作为已推迟的 RFC #3035 的一个临时补丁,提供实际可用的替代方案,同时支持进一步实验。作为补丁方案,它在 artifact dependencies 支持方面存在不足。一个可能的改进是:将该指令命名为 artifact,并在依赖某个包时自动创建 artifact dependency 变量,而无需等待 #3544 实现。产物名称可以用目标路径来标识,但如果允许目录作为目标,就无法保证唯一性。依赖包的 artifact 也将隐式包含在依赖声明中,而非像常规 artifact dependency 那样显式声明。
待解决的开放问题
- 指令名称叫什么?
copy、artifact、copy-artifact还是其他? - 当源或目标是没有尾随
/的目录时,复制语义是什么? - 源路径和目标路径之间用什么分隔符?
- 复制目录时,是检查目录本身的冲突还是其内容的冲突?
- 是否以及如何与 artifact dependency 系统挂钩?
一个可行的推进路线是先不建立与 artifact dependency 的连接,确保设计在未来可以兼容,或者先做一个低层次的设计,日后在此基础上构建完整的 artifact 解决方案。
九、目标目录锁定(target dir locking)
背景与动机
构建速度慢的一个重要原因是被另一个并发构建阻塞。典型场景是:在命令行运行 cargo check 的同时,rust-analyzer 也在后台执行 cargo check。由于 target/<platform>/<profile> 整个目录由单一锁管理,两者会互相阻塞。
ranger-ross 正在推进跨项目缓存(#5931),构建目录布局变更 是其中的一个基础步骤。在此之上,他们决定探索更细粒度的目标目录锁方案,作为面向用户的可交付成果,同时也为跨项目缓存(假设能够实现缓存复用)探索可行的锁机制。
构建目录 vs 产物目录的拆分
目标目录通常包含两个概念:存放中间产物(如 rlib)的构建目录,以及存放最终产物(如可执行文件)的产物目录。
由于当前大部分的冲突问题来自 cargo check 与其他检查/运行命令的并发执行,ranger-ross 采用了一个简单直接的方案:将构建目录锁和产物目录锁拆分,并在不生成最终产物时避免获取产物目录锁。这一改动不会对 cargo build、cargo run、cargo test、cargo bench 的并发运行有所帮助,但这类场景本身并不常见,问题优先级相对较低。
细粒度锁的技术挑战
针对单个构建单元(build unit)的独立锁方案,情况要复杂得多。直接的实现方式是:每个构建单元为自身获取独占锁,并为所有依赖获取共享锁。然而这带来了若干复杂问题:
问题一:阻碍流水线编译(pipelined compilation)
流水线编译是 Rust 1.38 引入的优化,允许后续构建单元在仅需要 .rmeta 时不必等待 .rlib 生成完毕。如果下游构建单元需要持有上游依赖的共享锁,就意味着必须等到 rlib 生成完毕才能开始,会破坏这一优化。
问题二:性能开销
每个构建单元需要为每个依赖分别获取一把锁,锁的数量与依赖规模成正比,带来不可忽视的性能开销。
问题三:文件描述符限制
每个构建单元为每个依赖各持一把锁,很容易触及操作系统的 ulimit 限制,尤其是在依赖较多的项目中。
单一大锁方案的局限
团队曾希望用一把"读/追加 xor 独占写"的大锁来避开上述问题,但这里存在一个根本性困难:构建单元的缓存条目是可变的(mutable),在实际构建开始前无法预知某个构建单元是否会修改缓存。
可以通过比较 Fingerprint 与 Metadata::c_metadata 来了解哪些构建输入会导致缓存条目发生变化。
将缓存条目设计为不可变
如果将缓存条目设计为不可变,则每次 rust-analyzer 在编辑源文件后执行 cargo check 都会产生一个新的缓存条目,数量会急剧膨胀。这意味着细粒度锁方案将不得不依赖构建目录 GC(#5026)的实现,并且可能需要针对不同类型的缓存条目设置不同的回收策略,以便 Cargo 保留最相关的缓存条目。
不可变缓存条目确实带来一个好处:在不同分支之间切换时,无需重新构建。
然而还有一个更大的问题:Cargo 需要在构建开始时发现源文件,才能判断某个缓存条目是否存在。对于跨项目缓存来说,这不是问题,因为该缓存只存储不可变包(immutable packages)的条目。
优化方向
ranger-ross 在探索单构建单元锁方案时,发现了一个优化思路:让顶层构建操作持有懒加载(lazily acquired)的锁,从而确保每个构建单元最多只需要一把锁。
十、结构化日志(structured logging)
背景
这是一个 Rust 项目目标,旨在为 Cargo 添加结构化日志,让用户能够查看历史构建的 timings(计时信息) 和重新构建的原因,从而避免"为什么这次构建这么慢"或"为什么这个包被重新构建了"这类难以复现的困惑。
进展
weihanglo 在 Cargo 中添加了结构化日志支持(#16150),并持续完善了相关实现(#16179、#16203、#16282、#16303、#16346、#16350、#16378),并添加了 cargo report timings 命令(#16377)。
如何启用
在 ~/.cargo/config.toml 中添加以下配置即可在 nightly 工具链上启用:
toml
[build.analysis]
enabled = true
稳定化前的待解决问题
迈向稳定化的路上还有许多问题需要解决:
- 数据 schema 的设计 :是否要将该 schema 与 Cargo 现有的 JSON 输出格式统一(#8283)。如果统一,将提升 Cargo 的 JSON 输出质量,并为
cargo fix的新架构(更快速、更灵活)解除阻碍(参见 cargo-fixit#68)。相关讨论可参考 Zulip。 - 新增
cargo report命令的接口设计
十一、配置文件 include(-Zconfig-include)正式稳定化
功能简介
-Zconfig-include 允许 .cargo/config.toml 文件包含其他配置文件,例如:
toml
include = ["frodo.toml", "samwise.toml"]
这一特性自 2019 年起便处于不稳定状态,期间存在诸多顾虑。2024 年曾有一次稳定化尝试,希望先稳定一个基础版本,未来再扩展;但也有人担心应当先确保未来扩展方向可行,再推进稳定化。
本轮关键改进
weihanglo 接手了这项工作,逐步解决了各项遗留问题:
数组合并支持 :在 #16103 中支持了跨层级的 table 数组合并(此前仅支持字符串数组合并)。
include 支持 table 数组语法 :在 #16174 中允许 include 使用 table 数组格式进行声明,这为未来添加字段来自定义 include 行为(例如声明某个 include 为可选:#16180)预留了空间。
glob 与模板语法的处理 :原计划通过 config table 上的字段来选择性开启 glob 或模板支持,但 Cargo 其他部分已直接支持模板或 glob,逻辑不一致。最终决定在检测到 glob 或模板语法时直接报错(#16285),保留未来向任一方向演进的灵活性。
语法形式的取舍
稳定化讨论中出现了一个问题:哪些语法形式应被支持和推荐?目前支持以下四种写法:
toml
# 方式一:单字符串
include = "a.toml"
# 方式二:字符串数组
include = [
"a.toml",
"b.toml",
]
# 方式三:inline table 数组(推荐)
include = [
{ path = "a.toml" },
{ path = "b.toml" },
]
# 方式四:Array-of-Tables
[[include]]
path = "a.toml"
[[include]]
path = "b.toml"
在 TOML 中,inline table 数组和 Array-of-Tables 逻辑上等价,但选择哪种语法对用户体验和设计影响各异:
- 如果以 Array-of-Tables 为推荐语法,则
include = ""和include = [""]这两种简写形式应该废弃,因为它们在需要新字段时必须整体改写结构。 - inline table 数组的问题是它是一个顶层 key,在 TOML 中根表是顺序相关的(必须放在最前面),这可能令人困惑。但换个角度看,
include本身就是特殊的,它改变了文件的处理方式,放在最前面反而能引起用户注意。
最终决定:在文档和未来的 .cargo/config.toml 风格指南中,推荐使用 inline table 数组语法,并废弃 include = "" 这一过于特化的简写形式(#16298)。weihanglo 在 #16301 中更新了相关文档。
用户级配置与项目级配置共存
.cargo/config.toml 的一个长期困扰是:如何同时支持项目级和用户级配置(#14565)。
如果项目提交了配置文件,用户若想添加自己的配置,唯一的办法是将仓库放入某个子目录,再在父目录中放用户配置;如果项目没有提交配置文件,用户可以创建,但默认不会被 .gitignore 忽略,用户常常会误将其提交上去。
借助 -Zconfig-include 稳定化后,项目可以在 .gitignore 中忽略 .cargo/config.user.toml,并在配置文件中写:
toml
include = [
{ path = "config.user.toml", optional = true }
]
不过,这需要项目主动更新 MSRV 并采用这一约定。
关于是否在稳定化前预留 .cargo/config.user.toml 这个名字作为自动加载的文件,团队进行了讨论,但认为这会影响 --config PATH 的现有用户,不值得在此时阻塞稳定化。另外,.cargo/config/ 目录名目前因 Cargo 默认将 .cargo/config 视为 TOML 文件而无法使用,这也是另一种潜在的保留手段。
最终,weihanglo 在 #16284 中完成了该特性的稳定化。
十二、pubtime(发布时间戳)
背景与需求
近期,社区对"依赖版本渐进式升级控制"的需求日益增加------即允许用户控制某个包的版本必须发布多久后才能被升级到。这一功能的基础是 Cargo 需要知道 registry 中各包版本的发布时间。
实现进展
Cargo 的依赖解析基于 Registry Index 中的 Summary 对象。为此,需要在该格式中扩展发布时间字段(#15491),同时需要 crates.io 完成历史数据回填,并优雅地处理不提供该信息的 registry。
在与 crates.io 团队讨论后,epage 在 #16265 中同时实现了 Cargo 侧的支持以及"时间旅行依赖解析"(#5221)------一种供用户探索该功能的实验性接口。Turbo87 在 crates.io 的 staging 实例上实现了相应支持(crates.io#12315)。
为推进 Summary 字段的稳定化,Cargo 团队讨论了相关设计,epage 根据反馈在 #16369 中完成了调整,并在 #16372 中提交了稳定化请求。
时间旅行依赖解析
通过 cargo generate-lockfile --publish-time 2025-12-01T01:01:01Z 即可启用"时间旅行"功能,让 Cargo 只考虑在指定时间点之前已发布的版本。实现上相对直接,但稳定化则面临更多挑战:
- 该功能仅适用于提供了发布时间信息的 registry
- 即便如此,功能也不完整:目前只追踪最新的
yanked状态,而非 yank/unyank 的历史记录 - 需要向用户清晰传达该功能的局限性,以及功能激活时实际发生了什么
这些未解决的问题正在 #16271 中持续跟踪。
minimumReleaseAge 的探索
在 #15973 中,正在持续探索 minimumReleaseAge 功能的需求分析、先例调研和设计方案。
十三、杂项改进
本轮还有若干值得关注的改进,以下逐一说明:
cargo clean 新增 --workspace 标志 :osiewicz 在 #16263 中为 cargo clean 添加了 --workspace 选项,方便在 workspace 场景下一次性清理所有成员包。
cargo clean 提速 :osiewicz 在 #16264 中加速了 cargo clean -p 和 cargo clean --workspace 的执行效率。
git 浅克隆支持 net.git-fetch-with-cli :weihanglo 在 #16156 中为 git 浅克隆 添加了 net.git-fetch-with-cli 配置支持。
拼写检查 :Muscraft 在 #16122 中为 Cargo 的源代码和文档添加了拼写检查。
cargo tree 的交互式 TUI 探索 :延续 1.90 周期的讨论,orhun 正在 cargo-tree-tui 项目中探索一种类似交互式 pager 的 TUI 界面,用于 cargo tree,并以此作为将 TUI 扩展到其他命令的试验场(参见 Zulip)。
Cargo Script 持续打磨 :延续 1.92 周期,epage 对 Cargo Script 进行了进一步完善(#16334、#16248、#16169)。
加速 rustdoc 构建(不稳定特性) :weihanglo 在 #16309 中提交了一个不稳定特性,用于提升 rustdoc 的构建速度。
十四、暂无进展的关注领域
以下是 Cargo 团队成员持续关注、但本周期没有可报告进展的方向:
需要负责人的项目目标:
已准备好开发的特性:
规划阶段的特性:
- 禁用默认 features
- RFC #3416:
features元数据(包含可见性 RFC #3487、废弃 RFC #3486 等子议题) - Pre-RFC:全局互斥 features
- RFC #3553:Cargo SBOM Fragment
- OS 原生配置/缓存目录支持(即 XDG 支持)
十五、如何参与贡献
如果你有改进 Cargo 的想法,建议先查阅 issue backlog,再在 Internals 论坛 上展开讨论。
如果你希望推动某个未在本文中提及的具体 issue,可以从以下几个角度入手:
- 整理现有讨论,归纳核心分歧与共识(可参考:Docker 层缓存支持、Cargo.lock 策略变更、MSRV 感知解析器)
- 调研其他生态系统的先例,帮助在熟悉的基础上设计解决方案
- 梳理 Cargo 内部的相关问题与已有方案,判断是否在正确的抽象层次上解决问题
- 在上述工作的基础上,提出一个兼顾现有信息和 Cargo 兼容性要求的解决方案(示例)
Cargo 团队在 Zulip 上提供针对 S-accepted issues 的 mentor 支持,并定期举办贡献者 Office Hours。
如果你想参与本文提到的某个较大项目,建议先通过修复一些 issue 来熟悉流程和期望,这将使后续协作更加顺畅。对于不需要 mentor 的 issue,则需要你能够更多地独立推进。
原文作者:Ed Page,代表 Cargo 团队发布。原文链接:https://blog.rust-lang.org/inside-rust/2026/01/07/this-development-cycle-in-cargo-1.93/