高级迁移策略

迁移的工作原理

cargo fix --edition 的工作原理是在您的项目上运行相当于 cargo check 的命令,并启用特殊的 lints,这些 lints 将检测在下一个版本中可能无法编译的代码。这些 lints 包括有关如何修改代码以使其与当前版本和下一个版本兼容的说明。cargo fix 将这些更改应用于源代码,然后再次运行 cargo check 以验证修复是否有效。如果修复失败,它将撤消更改并显示警告。

将代码更改为同时与当前版本和下一个版本兼容,可以更容易地增量迁移代码。如果自动迁移没有完全成功,或者需要手动帮助,您可以在更改 Cargo.toml 以使用下一个版本之前,在原始版本上进行迭代。

cargo fix --edition 应用的 lints 是 lint 组 的一部分。例如,从 2018 迁移到 2021 时,Cargo 使用 rust-2021-compatibility 组的 lints 来修复代码。有关使用单个 lints 帮助迁移的提示,请查看下面的 部分迁移 部分。

cargo fix 可能会多次运行 cargo check。例如,在应用一组修复后,这可能会触发需要进一步修复的新警告。Cargo 会重复此过程,直到不再生成新的警告。

迁移多个配置

cargo fix 一次只能处理一个配置。如果您使用 Cargo 功能条件编译,则可能需要使用不同的标志多次运行 cargo fix

例如,如果您的代码使用 #[cfg] 属性为不同的平台包含不同的代码,则可能需要使用 --target 选项运行 cargo fix 以修复不同的目标。如果您没有可用的交叉编译,这可能需要在机器之间移动您的代码。

类似地,如果您对 Cargo 功能有条件,例如 #[cfg(feature = "my-optional-thing")],建议使用 --all-features 标志,以便 cargo fix 可以迁移这些功能门后面的所有代码。如果您想单独迁移功能代码,可以使用 --features 标志一次迁移一个。

迁移大型项目或工作区

您可以增量迁移大型项目,以便在遇到问题时更容易处理。

Cargo 工作区 中,每个包都定义了自己的版本,因此该过程自然涉及一次迁移一个包。

Cargo 包 中,您可以一次迁移整个包,也可以一次迁移单个 Cargo 目标。例如,如果您有多个二进制文件、测试和示例,则可以将特定的目标选择标志与 cargo fix --edition 一起使用,以仅迁移该目标。默认情况下,cargo fix 使用 --all-targets

对于更高级的情况,您可以在 Cargo.toml 中为每个目标指定版本,如下所示

[[bin]]
name = "my-binary"
edition = "2018"

这通常不需要,但如果您有很多目标并且难以将它们一起迁移,则可以选择这样做。

使用损坏的代码进行部分迁移

有时,编译器建议的修复可能无法正常工作。发生这种情况时,Cargo 将报告一个警告,指示发生了什么以及错误是什么。但是,默认情况下,它会自动撤消所做的更改。将代码保持在损坏状态并手动解决问题可能会有所帮助。有些修复可能是正确的,而损坏的修复可能大部分是正确的,但只需要稍微调整一下。

在这种情况下,请将 --broken-code 选项与 cargo fix 一起使用,以告诉 Cargo 不要撤消更改。然后,您可以手动检查错误并调查修复它需要什么。

增量迁移项目的另一种选择是一次应用一个单独的修复。您可以通过将单个 lints 添加为警告来做到这一点,然后运行 cargo fix(不带 --edition 标志)或使用您的编辑器或 IDE 应用其建议(如果它支持“快速修复”)。

例如,2018 版本使用 keyword-idents lint 来修复任何冲突的关键字。您可以在每个 crate 的顶部添加 #![warn(keyword_idents)](例如在 src/lib.rssrc/main.rs 的顶部)。然后,运行 cargo fix 将仅应用该 lint 的建议。

您可以在 lint 组 页面中查看为每个版本启用的 lints 列表,或运行 rustc -Whelp 命令。

迁移宏

某些宏可能需要手动工作才能为下一个版本修复它们。例如,cargo fix --edition 可能无法自动修复生成在下一个版本中不起作用的语法的宏。

这对于 proc 宏macro_rules 样式的宏来说都可能是一个问题。如果在同一个 crate 中使用 macro_rules 宏,则有时可以自动更新它们,但在某些情况下则不能。proc 宏通常根本无法自动修复。

例如,如果我们将包含此(人为的)宏 foo 的 crate 从 2015 迁移到 2018,则 foo 将不会自动修复。

#![allow(unused)]
fn main() {
#[macro_export]
macro_rules! foo {
    () => {
        let dyn = 1;
        println!("it is {}", dyn);
    };
}
}

当在 2015 版本的 crate 中定义此宏时,由于宏卫生(下面讨论),它可以从任何其他版本的 crate 中使用。在 2015 年,dyn 是一个普通的标识符,可以不受限制地使用。

但是,在 2018 年,dyn 不再是有效的标识符。当使用 cargo fix --edition 迁移到 2018 时,Cargo 根本不会显示任何警告或错误。但是,从任何 crate 调用 foo 时都将无法工作。

如果您有宏,我们建议您确保您有测试可以完全覆盖宏的语法。您可能还想通过在多个版本的 crate 中导入和使用宏来测试它们,以确保它在任何地方都能正常工作。如果遇到问题,您需要通读本指南的章节,以了解如何更改代码以使其在所有版本中都能正常工作。

宏卫生

宏使用一种称为“版本卫生”的系统,其中宏中的标记用它们来自哪个版本进行标记。这允许从不同版本的 crate 中调用外部宏,而无需担心从哪个版本调用它。

让我们仔细看看上面的示例,该示例定义了一个使用 dyn 作为标识符的 macro_rules 宏。如果该宏是在使用 2015 版本的 crate 中定义的,则该宏可以正常工作,即使它是从 2018 版本的 crate 中调用的,其中 dyn 是一个关键字,这通常会导致语法错误。let dyn = 1; 标记被标记为来自 2015,编译器会在代码扩展到的任何地方记住这一点。解析器查看标记的版本以了解如何解释它。

当在定义它的 crate 中将版本更改为 2018 时,就会出现问题。现在,这些标记被标记为 2018 版本,并且这些标记将无法解析。但是,由于我们从未从我们的 crate 中调用过该宏,因此 cargo fix --edition 从未有机会检查该宏并修复它。

文档测试

目前,cargo fix 无法更新 文档测试。在更新 Cargo.toml 中的版本后,您应该运行 cargo test 以确保一切仍然通过。如果您的文档测试使用了新版本不支持的语法,您将需要手动更新它们。

在极少数情况下,您可以手动设置每个测试的版本。例如,您可以在三个反引号上使用 edition2018 注释 来告诉 rustdoc 使用哪个版本。

生成的代码

如果您的构建脚本在编译时生成 Rust 代码,则自动修复也无法应用(请参阅代码生成示例)。在这种情况下,如果您最终得到的代码在新版本中无法正常工作,则需要手动更改构建脚本以生成兼容的代码。

迁移非 Cargo 项目

如果您的项目没有使用 Cargo 作为构建系统,则仍然可以使用自动 lints 来帮助迁移到下一个版本。您可以通过启用相应的lint 组来启用迁移 lints,如上所述。例如,您可以使用 #![warn(rust_2021_compatibility)] 属性或 -Wrust-2021-compatibility--force-warns=rust-2021-compatibility CLI 标志

下一步是将这些 lints 应用于您的代码。这里有几个选项

  • 手动阅读警告并应用编译器建议的建议。
  • 使用支持自动应用建议的编辑器或 IDE。例如,Visual Studio CodeRust Analyzer 扩展 能够使用“快速修复”链接自动应用建议。许多其他编辑器和 IDE 都有类似的功能。
  • 使用 rustfix 库编写迁移工具。这是 Cargo 在内部用于获取编译器的 JSON 消息 并修改源代码的库。查看 examples 目录 以获取有关如何使用该库的示例。

在新版本中编写惯用代码

版本不仅与新功能和删除旧功能有关。在任何编程语言中,习惯用法都会随着时间的推移而改变,Rust 也不例外。虽然旧代码将继续编译,但今天可能会使用不同的习惯用法来编写。

例如,在 Rust 2015 中,外部 crate 必须使用 extern crate 列出,如下所示

// src/lib.rs
extern crate rand;

在 Rust 2018 中,不再需要包含这些项。

cargo fix 具有 --edition-idioms 选项,可以自动将其中一些习惯用法转换为新语法。

警告:当前的“习惯用法 lints”已知存在一些问题。它们可能会提出不正确的建议,从而导致编译失败。当前的 lints 是

以下说明仅建议那些愿意解决一些编译器/Cargo 错误的勇敢者使用!如果遇到问题,您可以尝试上述--broken-code 选项,以尽可能多地取得进展,然后手动解决剩余问题。

解决了这个问题后,我们可以指示 Cargo 使用以下命令修复我们的代码片段

cargo fix --edition-idioms

之后,src/lib.rs 中带有 extern crate rand; 的行将被删除。

我们现在更习惯了,而且我们不必手动修复我们的代码!