Cargo 的本开发周期:1.77

2024 年 2 月 13 日 · Ed Page 代表 Cargo 团队

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 addcargo remove 合并到 cargo 中,cargo-edit 中最后一个要合并的主要工具是 cargo upgrade。目前,我们仅关注不兼容的升级(#12425),将修改其他情况下版本要求的考虑延迟处理(#10498)。

到目前为止,重点一直在完善 cargo update,包括

在本开发周期中,我们在 #13372 中添加了对版本落后依赖项的突出显示,为所有 cargo 用户提供了 cargo-outdated 的一个子集(另请参见 #4309)。

在审查期间,该 PR 被指出未遵循我们的控制台输出样式指南。这是一个“复制现有代码样式”的案例。为了减少将来发生这种情况的可能性,#13410 使我们的控制台输出更符合样式指南。

剩余的任务是添加一个 --breaking 标志,并扩展 --precise <breaking>,以便修改版本要求。

cargo update --precise <yanked>

1.76 更新

此前,cargo 团队批准了选择已撤回的包。weihanglo#13333 中提供了实现,该实现已被合并。它正在进行一轮测试,然后才会稳定化。

这对 cargo-semver-checks 很重要。当前的解决方案没有完全解决他们的需求。我们需要将此从 --precise 选择已撤回包扩展到 Cargo 考虑已撤回包但具有最低优先级。这在已撤回包方面打开了很大的空间,我们希望在 --precise 支持合并后进一步评估剩余的用例,看看是否值得。

-Zcheck-cfg

1.76 更新

Urgau 和我讨论了 rustc --check-cfg 参数的一些不一致的语法。为 --cfg 定义一组值的语法被重载了,因此空集被视为无值。在实践中,这意味着如果你有 #[cfg(feature = "foo")]features 表为空,你会收到关于 features 未定义的警告,而不是关于值 foo 未定义的警告。这在 rust-lang/rust#119473rust-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 更新

正如在 1.76 文章中提到的,Cargo 团队正在努力更新 annotate-snippets,使其看起来像 rustc 的消息。最初的意图是让所有 Rust 项目的诊断渲染器使用这个 crate 来实现统一的外观和感觉。这项工作在 rustc 方面陷入停滞,在清理 rustc 的过程中有人建议删除相关代码。这再次恢复了关于统一渲染器的讨论。最终决定让 Cargo 作为这项工作的试验台,因为它的用例更简单,对更丰富的错误消息没有现有的期望。这将有助于弥合与 rustc 需求之间的差距。

说到像 rustc,MuscraftPR 已合并,用于使用与 rustc 相同的颜色方案。

将类似 rustc 的消息添加到 cargo 的第一阶段工作已在 #13172 中合并。我们收到了一份关于 panic 的报告(已在 #13375 中修复),该报告突出了 TOML 解析消息质量不高的问题,因此这也得到了修复(#13376)。

当未请求 debuginfo 时剥离 std 的 debuginfo

1.76 更新

此前,我们讨论了当 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 metadataid 字段

1.76 更新

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 更新

截至 1.76 版本,在语法方面存在两个问题

  • infostring 的含义是由 rustc 拥有还是由使用它的工具拥有
  • 在 markdown 中(例如在 Issues 中)嵌套 cargo 脚本时,使用反引号会造成混淆

关于 infostrings 的讨论回溯到它的目的。Rustc 已经有 #[attributes] 可以工作,不需要这种新语法。如果非要说的话,重点应该放在改进 attributes 上。这种新语法是围绕外部工具的需求设计的,这些工具无法轻松处理 attributes。考虑到这一点,有人建议让外部工具来定义它。

如果我们同意这一点,那么我们要求使用 infostring 的权宜之计就不复存在了,这将减少最小语法,并使摆脱 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 方面,仍然存在如何处理 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)

1.76 更新

在处理此 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(公共/私有依赖项)

1.76 更新

在跟踪问题中有人提出担忧,认为公共依赖项在稳定化时需要提高 MSRV,这将减缓该功能的采用。到目前为止,我们的流程一直专注于要求提高 MSRV 来采用新功能,因为这是确保用户意图得到保留的安全默认设置。例如,对于different-binary-name,忽略 filename 字段而不是报错,会导致意外结果。据我所知,Cargo 首次在稳定版中将不稳定的 Cargo.toml 字段视为未使用键,是 package.rust-version,因为它仅用于诊断目的。随后在[lints]中也重复了这一点。我们澄清了不稳定的特性文档,以便更容易评估不要求提高 MSRV 的替代方案。对于公共依赖项,我们决定在稳定版中发出警告而不是报错(#13340)。
虽然我们无法改变过去,但一些编译器问题(rust-lang/rust#71043rust-lang/rust#119428)使得此功能何时稳定化尚不明确,因此我们可能有足够的间隙来证明这项工作的合理性。我们决定通过 Cargo.tomlcargo-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))".dependenciesrust-lang/rust#64797)或 target."cfg(version(1.70.0))".dependenciesrust-lang/rust#64796)之类的依赖项。

构建脚本指令

1.76 更新

构建脚本通过打印的特殊命令与 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 的行为不正确。

第一步可能是向用户提供警告,说明工具链正在被忽略。

杂项

没有进展的重点领域

这些是 Cargo 团队成员感兴趣的领域,在本开发周期内没有可报告的进展。

准备开发

需要设计和/或实验

规划中

如何提供帮助

如果您有改进 cargo 的想法,我们建议您首先查看我们的待办事项列表,然后在 Internals 上探讨该想法。

如果您希望解决某个此处未讨论的特定问题,您可以采取一些步骤来帮助推动其进展,包括

  • 总结现有讨论(示例:更好地支持 docker 层缓存Cargo.lock 策略的改变MSRV 感知的 resolver
  • 记录其他生态系统的现有技术,以便我们可以在他人工作的基础上进行构建,并在合理的情况下为用户创建熟悉的东西
  • 记录 Cargo 内的相关问题和解决方案,以便我们确定是否在正确的抽象层解决问题
  • 在这些帖子的基础上,提出一个考虑上述信息和 cargo 兼容性要求的解决方案(示例

我们可以在 zulip 上为 S-accepted 问题提供指导,您也可以在贡献者办公时间与我们实时交流。如果您想参与此处提及的较大项目之一,并且刚开始,修复一些问题将有助于您熟悉流程和期望,从而使事情进展更顺利。如果您想在没有指导的情况下解决问题,那么您需要自己完成的工作的要求会更高。