高级迁移策略
迁移的工作原理
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.rs
或 src/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 Code 和 Rust 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 是
- 版本 2018
- 版本 2021 没有任何习惯用法 lints。
以下说明仅建议那些愿意解决一些编译器/Cargo 错误的勇敢者使用!如果遇到问题,您可以尝试上述的
--broken-code
选项,以尽可能多地取得进展,然后手动解决剩余问题。
解决了这个问题后,我们可以指示 Cargo 使用以下命令修复我们的代码片段
cargo fix --edition-idioms
之后,src/lib.rs
中带有 extern crate rand;
的行将被删除。
我们现在更习惯了,而且我们不必手动修复我们的代码!