默认 Cargo 功能解析器

概要

  • Cargo.toml 中,edition = "2021" 意味着 resolver = "2"

详情

自 Rust 1.51.0 起,Cargo 已选择性地支持新的功能解析器,该解析器可以通过在 Cargo.toml 中使用 resolver = "2" 来激活。

从 Rust 2021 开始,这将成为默认设置。也就是说,在 Cargo.toml 中写入 edition = "2021" 将意味着 resolver = "2"

解析器是工作区的全局设置,并且该设置在依赖项中被忽略。该设置仅适用于工作区的顶级包。如果您正在使用虚拟工作区,您仍然需要在 [workspace] 定义中显式设置resolver 字段,如果您想选择加入新的解析器。

新的功能解析器不再合并以多种方式依赖的 crate 的所有请求的功能。有关详细信息,请参阅Rust 1.51 的公告

迁移

没有用于更新新解析器的自动化迁移工具。对于大多数项目,更新后通常只有很少或没有更改。

当使用 cargo fix --edition 更新时,如果新解析器将使用不同的功能构建依赖项,Cargo 将显示报告。它可能看起来像这样

注意:切换到 2021 版本将启用 Cargo 中的版本 2 功能解析器。这可能会导致某些依赖项构建时启用的功能比以前少。有关解析器更改的更多信息,请访问 https://doc.rust-lang.net.cn/nightly/edition-guide/rust-2021/default-cargo-resolver.html
当构建以下依赖项时,将不再使用给定的功能

  bstr v0.2.16: default, lazy_static, regex-automata, unicode
  libz-sys v1.1.3 (as host dependency): libc

这让您知道某些依赖项将不再使用给定的功能进行构建。

构建失败

在某些情况下,您的项目在更改后可能无法正确构建。如果一个包中的依赖项声明假设在另一个包中启用了某些功能,而这些功能现在被禁用,则它可能无法编译。

例如,假设我们有这样的依赖关系

# Cargo.toml

[dependencies]
bstr = { version = "0.2.16", default-features = false }
# ...

在我们的依赖树中的某个地方,另一个包有这样的依赖关系

# Another package's Cargo.toml

[build-dependencies]
bstr = "0.2.16"

在我们的包中,我们一直在使用 bstr 中的 words_with_breaks 方法,该方法需要启用 bstr 的 "unicode" 功能。由于 Cargo 统一了两个包之间 bstr 的功能,这在历史上是可行的。但是,更新到 Rust 2021 后,新的解析器将构建两次 bstr,一次使用默认功能(作为构建依赖项),一次不使用任何功能(作为我们的正常依赖项)。由于 bstr 现在是在没有 "unicode" 功能的情况下构建的,因此 words_with_breaks 方法不存在,并且构建将失败,并显示缺少该方法错误。

这里的解决方案是确保使用您实际使用的功能声明依赖项。例如

[dependencies]
bstr = { version = "0.2.16", default-features = false, features = ["unicode"] }

在某些情况下,这可能是您无法直接控制的第三方依赖项的问题。您可以考虑向该项目提交补丁,以尝试为有问题的依赖项声明正确的功能集。或者,您可以从您自己的 Cargo.toml 文件中向任何依赖项添加功能。例如,如果上面给出的 bstr 示例是在某些第三方依赖项中声明的,您可以直接将正确的依赖项声明复制到您自己的项目中。只要它们与新解析器的统一规则匹配,这些功能将被统一。规则是

  • 对于当前未构建的目标,在平台特定依赖项上启用的功能将被忽略。
  • 构建依赖项和过程宏不与正常依赖项共享功能。
  • 开发依赖项不会激活功能,除非构建需要它们的target(如测试或示例)。

一个真实的例子是使用dieseldiesel_migrations。这些软件包提供数据库支持,并且使用功能选择数据库,如下所示

[dependencies]
diesel = { version = "1.4.7", features = ["postgres"] }
diesel_migrations = "1.4.0"

问题在于 diesel_migrations 有一个内部的过程宏,它本身依赖于 diesel,并且该过程宏假设它自己的 diesel 副本启用了与依赖关系图的其余部分相同的功能。更新到新解析器后,它无法构建,因为现在有两个 diesel 副本,为过程宏构建的副本缺少“postgres”功能。

这里的解决方案是将 diesel 作为具有所需功能的构建依赖项添加,例如

[build-dependencies]
diesel = { version = "1.4.7", features = ["postgres"] }

这会导致 Cargo 为主机依赖项(过程宏和构建依赖项)添加“postgres”功能。现在,diesel_migrations 过程宏将启用“postgres”功能,并且它将正确构建。

diesel 的 2.0 版本(目前正在开发中)没有这个问题,因为它已重组为没有此依赖项要求。

探索功能

cargo tree 命令已得到重大改进,以帮助迁移到新的解析器。cargo tree 可用于探索依赖关系图,并查看哪些功能正在启用,重要的是为什么它们正在启用。

一种选择是使用 --duplicates 标志(简写为 -d),该标志将告诉您何时多次构建一个包。以前面的 bstr 示例为例,我们可能会看到

> cargo tree -d
bstr v0.2.16
└── foo v0.1.0 (/MyProjects/foo)

bstr v0.2.16
[build-dependencies]
└── bar v0.1.0
    └── foo v0.1.0 (/MyProjects/foo)

此输出告诉我们 bstr 构建了两次,并显示了导致其在两种情况下都包含在内的依赖链。

您可以使用 -f 标志打印每个包正在使用的功能,如下所示

cargo tree -f '{p} {f}'

这告诉 Cargo 更改输出的“格式”,它将同时打印包和启用的功能。

您还可以使用 -e 标志来告知它要显示哪些“边”。例如,cargo tree -e features 将在每个依赖项之间显示每个依赖项添加的功能。此选项在使用 -i 标志来“反转”树时会变得更有用。这使您可以查看功能如何流入给定的依赖项。例如,假设依赖关系图很大,并且我们不太确定谁依赖于 bstr,以下命令将显示它

> cargo tree -e features -i bstr
bstr v0.2.16
├── bstr feature "default"
│   [build-dependencies]
│   └── bar v0.1.0
│       └── bar feature "default"
│           └── foo v0.1.0 (/MyProjects/foo)
├── bstr feature "lazy_static"
│   └── bstr feature "unicode"
│       └── bstr feature "default" (*)
├── bstr feature "regex-automata"
│   └── bstr feature "unicode" (*)
├── bstr feature "std"
│   └── bstr feature "default" (*)
└── bstr feature "unicode" (*)

此输出片段显示项目 foo 依赖于具有“default”功能的 bar。然后,bar 依赖于具有“default”功能的 bstr 作为构建依赖项。我们可以进一步看到 bstr 的 “default” 功能启用了 “unicode”(以及其他功能)。