Cargo 的这个开发周期:1.77
这是过去 6 周左右,即 Rust 1.77 的合并窗口期间,Cargo 开发方面发生的事情的总结。
本周期的插件
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 http://doc.rust-lang.net.cn/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 http://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 http://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 相同的配色方案。
向 cargo 添加类似 rustc 的消息的第一阶段在 #13172 中合并。我们收到了一份关于 panic 的报告(在 #13375 中修复),该报告突出显示了一条糟糕的 TOML 解析消息,因此也进行了修复(#13376)。
std
的调试信息
当未请求调试信息时,剥离
以前,我们讨论过在 debug=0
时隐式设置 strip = "debuginfo"
。来自 Kobzol 的正式提案被接受并在 #13257 中实施。通过此更改,默认 release
配置文件构建中将剥离 std
的调试符号。这更接近用户对 debug=0
的期望,并且也兑现了我们在 Cargo 文档中的承诺:完全没有调试信息。据观察,发布二进制文件小了约 3-4 MiB,并且在 Linux 上,编译略快。但是,macOS 上的编译可能会稍微慢一些(构建 cargo 大约慢 1%),因为它需要调用外部 strip
命令。另一个已知问题(#11641)是,在 macOS 上,它依赖于系统的 strip
,如果 strip
命令被不兼容的 strip
二进制文件所覆盖,则可能会失败。我们将继续监控它是否会成为 Rust 维护人员或用户的负担。有关更多详细信息,请参阅 Kobzol 的文章。
cargo metadata
的 id
字段
稳定
FCP 已完成,并且稳定 PR 已合并。
得益于每晚的测试,我们发现人们将 cargo metadata
的输出与 cargo build --message-format=json
的输出相关联,这是我们之前忽略的。因此,我们在 #13311 中将此稳定化扩展到了 --message-format=json
,并在 #13322 中添加了测试,以确保它们的输出是可互操作的。
设计讨论
当人们对调试构建进行基准测试时,减少意外
Rust 新用户的一个常见误区是,他们对代码进行基准测试时发现速度出奇地慢,而答案很简单,只需传递 --release
即可。jackh726 发起了一个讨论,探索如何帮助用户避免这个误区(另请参阅 #9446)。
默认配置文件 dev
针对快速反馈进行了优化,通过包含调试信息和激活 debug_assertions
使调试更容易。假设调试将是内部开发循环的一部分,只偶尔发布。随着 cargo check
的引入,对速度的需求略有降低。
不期望这样做的用户必须在所有编译器输出中注意到并解读 dev [未优化 + 调试信息]
。
头脑风暴正在进行中,但想法包括:
- 要求使用
--profile
- 调整状态行的文本
- 在状态行中添加表情符号或样式
- 支持在配置中为每个命令设置默认配置文件,并在未设置时发出警告
- 更改命令的默认配置文件
- 减少其他输出(在 #8889 中有所讨论)
在解决这个问题时,我们需要仔细权衡所有用户的需求,包括我们对向后兼容性的承诺。讨论正在进行中。
Cargo 脚本
截至 1.76 版本,语法方面存在两个问题
- 信息字符串的含义是归 rustc 所有还是归使用它的工具所有
- 反引号的使用使得在 Markdown 中嵌套 Cargo 脚本(如在 Issues 中)令人困惑
关于信息字符串的讨论可以追溯到其目的。Rustc 已经有 #[attributes]
可以工作,不需要这个新的语法。如果有的话,重点应该放在改进属性上。这个新语法是围绕无法轻易使用属性的外部工具的需求设计的。考虑到这一点,有人建议让外部工具来定义它。
如果我们同意这一点,那么我们要求信息字符串的临时措施就消失了,从而减少了最小语法,并且更容易摆脱 Markdown 代码围栏,避免了嵌套问题。在与 T-lang 的头脑风暴中,考虑了几种语法。目前,Cargo 支持所有这些语法,供人们试用(#13241, #13247)。
在讨论它们并评估用户报告后,包括 timClicks 的 反应视频,提出了以下语法:
#!/usr/bin/env cargo
---
[dependencies]
clap = { version = "4.2", features = ["derive"] }
---
use clap::Parser;
#[derive(Parser, Debug)]
#[clap(version)]
struct Args {
#[clap(short, long, help = "Path to config")]
config: Option<std::path::PathBuf>,
}
fn main() {
let args = Args::parse();
println!("{:?}", args);
}
语法 RFC 已 提议合并。
在 Cargo 方面,仍然存在如何处理配置文件的问题。
何时使用包或工作区?
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 要求更改解析器的行为。我们曾考虑使用一个新字段来控制这一点,但这会使行为比预期更静态。例如,我们可能希望在本地 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 要求将错误转换为默认拒绝的 lint。我们可能会弃用构建命令中的该标志,从而减少这种重载。我们认为没有理由为此而搁置 RFC,我们可以在 RFC 合并后解决 lockfile 的问题。
在 Pre-RFC 中,一位用户指出,他们的 cargo publish
在从其 MSRV 工具链运行时会失败。这是因为 Cargo 仅在你有二进制文件时才重用你的 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 发布了关于 SBOM 的内容
- 与 特性限制 一样,crates.io 现在也有了 依赖项限制
cargo fix
的速度可能比cargo check
慢得多。#13243 在某种程度上加快了它的速度。- 在 RFC #3529 之后,Internals: 通过中间目录与 monorepos 集成 被发布。
没有进展的重点领域
这些是 Cargo 团队成员感兴趣的领域,但在此开发周期内没有可报告的进展。
准备开发
- 将
cargo upgrade
合并到cargo update
中 - 工作区的
cargo publish
- 自动生成补全
- 通用化 cargo 的测试断言代码
- 使用预发布依赖项的
cargo update --precise
需要设计和/或实验
计划
如何提供帮助
如果您有改进 cargo 的想法,我们建议您首先检查我们的积压工作,然后在Internals上探索这个想法。
如果存在您希望解决但此处未讨论的特定问题,您可以采取以下一些步骤来帮助推动它向前发展
- 总结现有的对话(示例:更好地支持 docker 层缓存,
Cargo.lock
策略的变更,MSRV 感知的解析器) - 记录其他生态系统中的现有技术,以便我们可以基于其他人所做的工作进行构建,并在有意义的地方为用户提供熟悉的东西
- 记录 Cargo 中相关的问题和解决方案,以便我们了解我们是否在正确的抽象层进行解决
- 基于这些帖子,提出一个考虑到以上信息和 cargo 的兼容性要求的解决方案(示例)
我们可以在 zulip 上帮助指导人们解决 S-accepted 问题,您可以在贡献者办公时间与我们实时交谈。如果您希望帮助这里提到的某个更大的项目并且刚刚开始,解决一些问题将帮助您熟悉流程和期望,从而使事情进展更加顺利。如果您想在没有导师的情况下解决问题,那么您需要自己完成的工作的期望会更高。