Cargo 的本开发周期:1.77
这是过去 6 周 Cargo 开发进展的总结,这段时间大约是 Rust 1.77 的合并窗口期。
本周期插件
Cargo 不可能满足所有人的所有需求,尤其考虑到它必须维护的兼容性保证。插件在 Cargo 生态系统中扮演着重要角色,我们希望表彰它们。
本周期的特色插件是 cargo-watch,它会在源文件更改时重新运行 cargo 命令。关于将其合并到 cargo 中的讨论,请参阅 #9339。
感谢 LukeMathWalker 的建议!
实现
cargo new
优化 cargo new
在 Cargo 1.71 中获得了检测工作空间并自动继承其字段的能力,并在 Cargo 1.75 中更新了 workspace.members
。这些功能是分开实现的,字段继承没有考虑工作空间成员排除项,hi-rustin 在 #13261 中解决了这个问题。linyihai 随后在 #13391 中将工作空间包含逻辑限制为仅当发现的包已经包含 [workspace]
表时才执行。linyihai 还在 #13411 中,如果修改了 workspace.members
,则向用户添加了一个 note:
提示。
无论何时运行 cargo new
,你都会收到一条注释,指导你填写 Cargo.toml
的下一步。
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
虽然这有助于新的 Rust 程序员,但这增加了现有 Rust 程序员在每次调用时都必须删除的样板代码。为了兼顾这两类用户,我们正在尝试将其改为一个 note:
提示(#13371)。对我个人而言,我发现注释的内容(创建了一个包)出现在注释之后有点奇怪,所以在 #13367 中,我们将末尾打印 Created
状态改为在开头打印 Creating
状态。
使用以前的 Created
$ cargo new foo
Adding `foo` as member of workspace at `/home/epage/src/personal/cargo`
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.net.cn/cargo/reference/manifest.html
Created binary (application) `foo` package
使用新的 Creating
$ cargo new foo
Creating binary (application) `foo` package
Adding `foo` as member of workspace at `/home/epage/src/personal/cargo`
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.net.cn/cargo/reference/manifest.html
cargo upgrade
合并到 cargo update
将 随着 cargo add
和 cargo remove
合并到 cargo 中,cargo-edit
中最后一个要合并的主要工具是 cargo upgrade
。目前,我们仅关注不兼容的升级(#12425),将修改其他情况下版本要求的考虑延迟处理(#10498)。
到目前为止,重点一直在完善 cargo update
,包括
在本开发周期中,我们在 #13372 中添加了对版本落后依赖项的突出显示,为所有 cargo 用户提供了 cargo-outdated 的一个子集(另请参见 #4309)。
在审查期间,该 PR 被指出未遵循我们的控制台输出样式指南。这是一个“复制现有代码样式”的案例。为了减少将来发生这种情况的可能性,#13410 使我们的控制台输出更符合样式指南。
剩余的任务是添加一个 --breaking
标志,并扩展 --precise <breaking>
,以便修改版本要求。
cargo update --precise <yanked>
此前,cargo 团队批准了选择已撤回的包。weihanglo 在 #13333 中提供了实现,该实现已被合并。它正在进行一轮测试,然后才会稳定化。
这对 cargo-semver-checks 很重要。当前的解决方案没有完全解决他们的需求。我们需要将此从 --precise
选择已撤回包扩展到 Cargo 考虑已撤回包但具有最低优先级。这在已撤回包方面打开了很大的空间,我们希望在 --precise
支持合并后进一步评估剩余的用例,看看是否值得。
-Zcheck-cfg
Urgau 和我讨论了 rustc --check-cfg
参数的一些不一致的语法。为 --cfg
定义一组值的语法被重载了,因此空集被视为无值。在实践中,这意味着如果你有 #[cfg(feature = "foo")]
但 features
表为空,你会收到关于 features
未定义的警告,而不是关于值 foo
未定义的警告。这在 rust-lang/rust#119473、rust-lang/rust#119930 和 #13316 中得到了修复。更多详情请参阅Urgau 的评论。
该 lint 的一个不幸的误报是使用了 #[cfg_attr(docsrs, ...)]
在 docs.rs 上启用 nightly 特性的 crate。此警告只能通过添加 build.rs
来定义 docsrs
或使用 #![allow]
完全禁用此特性来解决。rustc
维护了一个手工编写的“众所周知”的 --cfg
列表,但这只是约定俗成,而不是官方支持的。所以我们决定看看是否可以通过让 docs.rs 代表用户将 --cfg docsrs
传递给 rustdoc 来获得官方支持。似乎有人对此感兴趣,所以我打开了 rust-lang/docs.rs#2389,Urgau 通过 rust-lang/docs.rs#2390 关闭了它。随后,--cfg docsrs
被添加到了 Cargo 的“众所周知”列表中。Cargo 似乎是更合适的归属,因为 docs.rs 通常与 crates.io 相关联,而 crates.io 通常与 Cargo 相关联,而 rustc 可以与其他构建系统一起使用。
cargo 团队就稳定化该特性进行了初步讨论。有人提出了性能方面的担忧,尤其是在有大量特性时,例如 windows。我们已经要求针对 windows
对 -Zcheck-cfg
进行基准测试,以验证其影响。我们也倾向于将此特性限制在“本地”包。这意味着只检查工作空间成员和路径依赖项,而不检查 git 和注册表依赖项。Cargo 和 rustc 已经有了“cap lints”的概念,用于隐藏来自非本地依赖项的警告。
一项呼吁测试的工作正在进行。
用户可控的 cargo 诊断信息
正如在 1.76 文章中提到的,Cargo 团队正在努力更新 annotate-snippets,使其看起来像 rustc 的消息。最初的意图是让所有 Rust 项目的诊断渲染器使用这个 crate 来实现统一的外观和感觉。这项工作在 rustc 方面陷入停滞,在清理 rustc 的过程中有人建议删除相关代码。这再次恢复了关于统一渲染器的讨论。最终决定让 Cargo 作为这项工作的试验台,因为它的用例更简单,对更丰富的错误消息没有现有的期望。这将有助于弥合与 rustc 需求之间的差距。
说到像 rustc,Muscraft 的 PR 已合并,用于使用与 rustc 相同的颜色方案。
将类似 rustc 的消息添加到 cargo 的第一阶段工作已在 #13172 中合并。我们收到了一份关于 panic 的报告(已在 #13375 中修复),该报告突出了 TOML 解析消息质量不高的问题,因此这也得到了修复(#13376)。
std
的 debuginfo
当未请求 debuginfo 时剥离
此前,我们讨论了当 debug=0
时隐式设置 strip = "debuginfo"
。来自 Kobzol 的正式提案被接受并在 #13257 中实现。通过这项更改,在默认的 release
构建配置中,std
的调试符号将被剥离。这更接近用户对 debug=0
的期望,同时也兑现了我们在Cargo 文档中的承诺:完全没有调试信息。据观察,发布二进制文件缩小了约 3-4 MiB,并且在 Linux 上编译略快。然而,在 macOS 上编译可能会稍慢(构建 cargo 慢约 1%),因为它需要调用外部的 strip
命令。另一个已知问题(#11641)是,在 macOS 上,它依赖于系统的 strip
命令,如果该命令被不兼容的 strip
二进制文件遮蔽,则可能会失败。我们将继续监控它是否会给 Rust 维护者或用户带来负担。更多详情请参阅Kobzol 的文章。
cargo metadata
的 id
字段
稳定化
FCP 已完成,稳定化 PR 已合并。
得益于 nightly 测试,我们发现我们忽略了人们将 cargo metadata
的输出与 cargo build --message-format=json
的输出关联起来使用,因此我们在 #13311 中也将此稳定化扩展到了 --message-format=json
,并在 #13322 中添加了测试以确保它们的输出可互操作。
设计讨论
减少人们对基准测试 debug 构建结果感到惊讶
对于 Rust 的新用户来说,一个常见的陷阱是他们对代码进行基准测试,发现其速度出乎意料地慢,而解决方法很简单,只需传递 --release
标志。 jackh726 发起了一次讨论,探讨如何帮助用户避免这个陷阱(另请参见 #9446)。
默认的配置文件 dev
针对快速反馈进行了优化,从而使调试更容易(通过包含调试信息和激活 debug_assertions
)。其假设是调试将是内部开发循环的一部分,发布只是偶尔进行。随着 cargo check
的引入,对速度的需求略有降低。
不了解此情况的用户必须在其所有的编译器输出中注意到并理解 dev [unoptimized + debuginfo]
。
头脑风暴正在进行中,想法包括
- 要求使用
--profile
- 调整状态行的文本
- 在状态行添加 emoji 或样式
- 在配置中支持每个命令的默认配置文件,并在未设置时发出警告
- 更改命令的默认配置文件
- 减少其他输出(在 #8889 中有所讨论)
在解决这个问题时,我们需要仔细权衡所有用户的需求,包括我们对向后兼容性的承诺。讨论正在进行中。
Cargo 脚本
截至 1.76 版本,在语法方面存在两个问题
- infostring 的含义是由 rustc 拥有还是由使用它的工具拥有
- 在 markdown 中(例如在 Issues 中)嵌套 cargo 脚本时,使用反引号会造成混淆
关于 infostrings 的讨论回溯到它的目的。Rustc 已经有 #[attributes]
可以工作,不需要这种新语法。如果非要说的话,重点应该放在改进 attributes 上。这种新语法是围绕外部工具的需求设计的,这些工具无法轻松处理 attributes。考虑到这一点,有人建议让外部工具来定义它。
如果我们同意这一点,那么我们要求使用 infostring 的权宜之计就不复存在了,这将减少最小语法,并使摆脱 markdown 代码围栏并避免嵌套问题变得更容易。在与 T-lang 进行头脑风暴时,考虑了几种语法。目前,cargo 中支持所有这些语法,供人们尝试(#13241、#13247)。
在讨论并评估用户报告后,包括 timClicks 的反应视频,提出了以下语法
#!/usr/bin/env cargo
---
clap =
---
use Parser;
该语法 RFC 已被提议合并。
在 Cargo 方面,仍然存在如何处理 profile 的问题。
何时使用包或工作空间?
Cargo 使得在同一个包中混合二进制文件和库变得足够容易:只需创建文件即可。问题在于人们很快就会遇到 Cargo.toml
设计的局限性。例如,通过执行 cargo add pulldown-cmark
,你会引入一个 CLI 解析器,这会减慢你的构建速度,你应该添加 --no-default-features
。
人们就此提出的问题包括
在尝试使用 RFC #3374 改进其中一个领域时,我们发现它会导致人们对特性统一的工作方式产生更多困惑,而这本身就是一个令人困惑的话题。
我们是否正在将方形钉子钉入圆形孔?正如一位团队成员所说:“在‘只添加一个二进制文件’和‘添加一个新包’之间存在一个盲区。”也许我们也可以考虑改进这方面的工作空间。为此,有人提出了一个思想实验:如果我们每个包只支持一个构建输出会怎样?痛点在哪里?
一个不足之处在于新用户不了解如何采用工作空间(另请参见 #5656)。提出的一个想法是创建一个工具,将一个包转换为工作空间+包。这类似于将 cargo 脚本转换为多文件包的提议。也许这种相似性可以指导我们设计这个工具的外观。这可能最好以第三方插件的形式进行实验。
管理所有包中的元数据存在开销,但最近 cargo new
工作中的工作空间继承帮助减少了这一点。
每个包默认使用多个文件和目录仍然存在开销。支持将 cargo 脚本作为工作空间成员可能有助于解决此问题。
所有这些中一个很大的不足是,你一次只能发布一个包(#1169)。我们将其列在下面作为我们的“重点领域”之一,并已将其提议用于 GSoC。发布不仅仅是上传,人们很可能需要采用像 cargo release 这样的工具。我们试图通过在我们的发布文档中提及这些工具来提高对其的认识。嵌套包也将减少一些发布开销。
例如,比较
$ cargo add pulldown-cmark
cargo add typos
与
$ cargo install pulldown-cmark
cargo install typos-cli
RFC #3383 是改进这方面的尝试。
虽然我们没有得出任何具体的结论,但我们至少对其中涉及的不同挑战有了更好的理解。
RFC #3537:让 Cargo 在选择依赖项时遵守最低支持 Rust 版本 (MSRV)
在处理此 RFC 的反馈时,作者带来了一个重大更新。部分目标是重新构建围绕不同用例的讨论,并确定如何优先处理这些不同的用例。在进行这种重新构建时,发现并解决了工作流程中更多棘手的问题。
此 RFC 要求更改 resolver 的行为。我们曾考虑使用一个新字段来控制此行为,但这会使行为比预期更静态。例如,我们可能希望在本地 cargo check
、某些 CI 作业和 cargo install
之间有不同的行为。如果我们有此功能,我们可以将其与 Edition 关联。由于我们已经开始了这条路,package.resolver
被忽略了。RFC 已更新,允许使用 package.resolver
控制默认行为,该字段的默认值将随下一个 Edition 更改。
在稳定化 Cargo.lock
v4 时,出现了在生成 lockfile 时是否应遵循 MSRV 的问题。在 #12861 中审查时,出现了另一个问题:如果传递了 --ignore-rust-version
,我们是否应该不这样做。今天,它的意思是“忽略 MSRV 不兼容错误”。有了 RFC 后,它也意味着“不基于 MSRV 进行解析”。Lockfile 将增加第三层含义。这是否太多了?在评估时,大多数人可能不会将 --ignore-rust-version
传递给构建命令,因为他们预计依赖树会发生变化,而是会更多地将此用于 cargo update
等 lockfile 命令。同样,我们预计对 cargo build --ignore-rust-version
的需求会减少,因为 RFC 要求将此错误转变为 deny-by-default lint。我们很可能会弃用构建命令上的该标志,从而减少这种重载。我们认为没有理由为此而阻碍 RFC 的进展,并且可以在 RFC 合并后处理 lockfile 的问题。
在 Pre-RFC 中,一位用户指出,从他们的 MSRV 工具链运行时,他们的 cargo publish
命令会失败。这是因为 Cargo 只有在你有 bin 文件时才会重用你的 lockfile,这导致选择了最新的依赖项。我们由此创建了 #13306,并将任何决定推迟。
RFC #3516(公共/私有依赖项)
在跟踪问题中有人提出担忧,认为公共依赖项在稳定化时需要提高 MSRV,这将减缓该功能的采用。到目前为止,我们的流程一直专注于要求提高 MSRV 来采用新功能,因为这是确保用户意图得到保留的安全默认设置。例如,对于different-binary-name
,忽略 filename
字段而不是报错,会导致意外结果。据我所知,Cargo 首次在稳定版中将不稳定的 Cargo.toml
字段视为未使用键,是 package.rust-version
,因为它仅用于诊断目的。随后在[lints]
表中也重复了这一点。我们澄清了不稳定的特性文档,以便更容易评估不要求提高 MSRV 的替代方案。对于公共依赖项,我们决定在稳定版中发出警告而不是报错(#13340)。
虽然我们无法改变过去,但一些编译器问题(rust-lang/rust#71043、rust-lang/rust#119428)使得此功能何时稳定化尚不明确,因此我们可能有足够的间隙来证明这项工作的合理性。我们决定通过 Cargo.toml
的 cargo-features
(对于始终需要此功能的人)和 -Z
(对于想在稳定版上构建的人)来支持启用此功能。
在审查 RFC #3560 时,有一条注释提到倾向于让所有 Edition 的警告级别保持一致。在 RFC #3516 中,我们倾向于随着 Edition 改变级别以减少干扰。在 Zulip 上讨论时,我们需要在稳定化之前重新评估这个决定。
回退依赖项
可选依赖项允许调用者选择更专业的实现,例如 winnow 有一个特性可以用 memchr 替换手动实现的字符串搜索。有时你可能想重用 crate 中现有的回退实现(另请参见 #1839)。我们讨论中使用的例子是 flate2
及其内部使用的压缩库。如果启用了两个后端,flate2
会优先使用一个,另一个会被忽略,但这会减慢用户构建速度。
这可以通过互斥的全局特性来解决,但在那之前有没有更小的解决方案?
例如,我们能否支持 target."cfg(not(feature = "miniz_oxide"))".dependencies
(另请参见 #8170)?在我们解析特性时,我们无法处理这些情况,因为我们是递增地构建特性集合,没有一个地方可以声明“这已经完成,让我们评估 not(features)
”。我们可以正常解析特性,然后检查 not(features)
并添加这些特性。但这会失败,因为这些新的依赖项不会执行特性解析。相反,我们需要循环运行特性解析,检查 not(features)
,然后将它们添加到下次评估的集合中。这实现起来很复杂,算法也很复杂,并且可能与 dev-dependencies 产生循环依赖。
我们能否让 build.rs
请求启用特性?就像上面一样,这会遇到实现和算法复杂性的问题。这还会导致发散解析的问题,即后来的包启用了改变已构建的早期包的解析结果的特性。
当回退是为了兼容旧版本的 Rust 时,替代方法可能是允许诸如 target."cfg(accessible(std::io::IsTerminal))".dependencies
(rust-lang/rust#64797)或 target."cfg(version(1.70.0))".dependencies
(rust-lang/rust#64796)之类的依赖项。
构建脚本指令
构建脚本通过打印的特殊命令与 cargo 通信。我们发现很难添加新的指令,因为我们在定义链接元数据时与用户共享了命名空间。我们通过将指令前缀从 cargo:
迁移到 cargo::
来解决这个问题,这将我们的命名空间与用户命名空间(cargo::metadata
)分开。
在执行此操作时,我们忽略了target.<triple>.<links>
也存在类似问题(另请参见 #12201)。由于新语法已在 1.76 中稳定,当时 1.76 处于 Beta 阶段,紧迫的问题是我们是否需要回滚并在将来一起完成这些工作。经过讨论,我们认为它们无需完全同步,尽管保持一致性是好的。我们现在正在 #13211 中跟踪这方面的配置问题。
Cargo 和 rustup
当 GuillaumeGomez 准备他们的关于自定义 linters 的博客文章时,他们遇到了一个问题,因为他们期望 cargo install --path <foo>
使用在 <foo>
发现的 rust-toolchain.toml
文件,而不是当前目录中的文件(#11036)。与 .cargo/config.toml
一样,rust-toolchain.toml
是一个“环境配置”,并且不遵从像 --manifest-path
这样的标志。然而,cargo 对 cargo install
(以及很快的 cargo script)中的 .cargo/config.toml
做了一个例外。我们能否对 rust-toolchain.toml
做类似的事情?
Rustup 是一个可选的工具链管理器,其本质上是与 Cargo 独立进行版本控制和分发的。我们在 Cargo 中确实对其进行了一些特殊处理,但更多地侧重于错误消息和性能。如果让 Cargo 承担 Rustup 在识别要使用的工具链版本方面的部分角色,我们将破坏抽象。我们还必须谨慎行事,因为存在对隔离工具链的需求,例如在 Linux 发行版中。更糟糕的是,当旧版本 Cargo 与新版本 Rustup 混合使用或新版本 Rustup 与旧版本 Cargo 混合使用时,可能会出现行为不匹配的问题,导致 Cargo 的行为不正确。
第一步可能是向用户提供警告,说明工具链正在被忽略。
杂项
- RFC #3553 已发布,用于 SBOMs
- 类似于特性限制,crates.io 现在有了依赖限制
cargo fix
可能比cargo check
慢得多。#13243 在一定程度上加快了它的速度。- 作为 RFC #3529 的后续,Internals: 通过中间目录与 mono-repos 集成 已发布。
没有进展的重点领域
这些是 Cargo 团队成员感兴趣的领域,在本开发周期内没有可报告的进展。
准备开发
- 将
cargo upgrade
合并到cargo update
- 工作空间的
cargo publish
- 自动生成补全
- 泛化 cargo 的测试断言代码
- 使用 pre-release 依赖项进行
cargo update --precise
需要设计和/或实验
规划中
- 禁用默认特性
- RFC #3416:
features
元数据- RFC #3485:描述 (descriptions)
- RFC #3487:可见性 (visibility)
- RFC #3486:弃用
- 不稳定特性
- RFC #3452:嵌套包
- OS 原生配置/缓存目录(例如 XDG 支持)
如何提供帮助
如果您有改进 cargo 的想法,我们建议您首先查看我们的待办事项列表,然后在 Internals 上探讨该想法。
如果您希望解决某个此处未讨论的特定问题,您可以采取一些步骤来帮助推动其进展,包括
- 总结现有讨论(示例:更好地支持 docker 层缓存,
Cargo.lock
策略的改变,MSRV 感知的 resolver ) - 记录其他生态系统的现有技术,以便我们可以在他人工作的基础上进行构建,并在合理的情况下为用户创建熟悉的东西
- 记录 Cargo 内的相关问题和解决方案,以便我们确定是否在正确的抽象层解决问题
- 在这些帖子的基础上,提出一个考虑上述信息和 cargo 兼容性要求的解决方案(示例)
我们可以在 zulip 上为 S-accepted 问题提供指导,您也可以在贡献者办公时间与我们实时交流。如果您想参与此处提及的较大项目之一,并且刚开始,修复一些问题将有助于您熟悉流程和期望,从而使事情进展更顺利。如果您想在没有指导的情况下解决问题,那么您需要自己完成的工作的要求会更高。