|
| 1 | +# Advanced migration strategies |
| 2 | + |
| 3 | +## How migrations work |
| 4 | + |
| 5 | +[`cargo fix --edition`][`cargo fix`] works by running the equivalent of [`cargo check`] on your project with special [lints] enabled which will detect code that may not compile in the next edition. |
| 6 | +These lints include instructions on how to modify the code to make it compatible on both the current and the next edition. |
| 7 | +`cargo fix` applies these changes to the source code, and then runs `cargo check` again to verify that the fixes work. |
| 8 | +If the fixes fail, then it will back out the changes and display a warning. |
| 9 | + |
| 10 | +Changing the code to be simultaneously compatible with both the current and next edition makes it easier to incrementally migrate the code. |
| 11 | +If the automated migration does not completely succeed, or requires manual help, you can iterate while staying on the original edition before changing `Cargo.toml` to use the next edition. |
| 12 | + |
| 13 | +The lints that `cargo fix --edition` apply are part of a [lint group]. |
| 14 | +For example, when migrating from 2018 to 2021, Cargo uses the `rust-2021-compatibility` group of lints to fix the code. |
| 15 | +Check the [Partial migration](#partial-migration-with-broken-code) section below for tips on using individual lints to help with migration. |
| 16 | + |
| 17 | +`cargo fix` may run `cargo check` multiple times. |
| 18 | +For example, after applying one set of fixes, this may trigger new warnings which require further fixes. |
| 19 | +Cargo repeats this until no new warnings are generated. |
| 20 | + |
| 21 | +## Migrating multiple configurations |
| 22 | + |
| 23 | +`cargo fix` can only work with a single configuration at a time. |
| 24 | +If you use [Cargo features] or [conditional compilation], then you may need to run `cargo fix` multiple times with different flags. |
| 25 | + |
| 26 | +For example, if you have code that uses `#[cfg]` attributes to include different code for different platforms, you may need to run `cargo fix` with the `--target` option to fix for different targets. |
| 27 | +This may require moving your code between machines if you don't have cross-compiling available. |
| 28 | + |
| 29 | +Similarly, if you have conditions on Cargo features, like `#[cfg(feature = "my-optional-thing")]`, it is recommended to use the `--all-features` flag to allow `cargo fix` to migrate all the code behind those feature gates. |
| 30 | +If you want to migrate feature code individually, you can use the `--features` flag to migrate one at a time. |
| 31 | + |
| 32 | +## Migrating a large project or workspace |
| 33 | + |
| 34 | +You can migrate a large project incrementally to make the process easier if you run into problems. |
| 35 | + |
| 36 | +In a [Cargo workspace], each package defines its own edition, so the process naturally involves migrating one package at a time. |
| 37 | + |
| 38 | +Within a [Cargo package], you can either migrate the entire package at once, or migrate individual [Cargo targets] one at a time. |
| 39 | +For example, if you have multiple binaries, tests, and examples, you can use specific target selection flags with `cargo fix --edition` to migrate just that one target. |
| 40 | +By default, `cargo fix` uses `--all-targets`. |
| 41 | + |
| 42 | +For even more advanced cases, you can specify the edition for each individual target in `Cargo.toml` like this: |
| 43 | + |
| 44 | +```toml |
| 45 | +[[bin]] |
| 46 | +name = "my-binary" |
| 47 | +edition = "2018" |
| 48 | +``` |
| 49 | + |
| 50 | +This usually should not be required, but is an option if you have a lot of targets and are having difficulty migrating them all together. |
| 51 | + |
| 52 | +## Partial migration with broken code |
| 53 | + |
| 54 | +Sometimes the fixes suggested by the compiler may fail to work. |
| 55 | +When this happens, Cargo will report a warning indicating what happened and what the error was. |
| 56 | +However, by default it will automatically back out the changes it made. |
| 57 | +It can be helpful to keep the code in the broken state and manually resolve the issue. |
| 58 | +Some of the fixes may have been correct, and the broken fix maybe be *mostly* correct, but just need minor tweaking. |
| 59 | + |
| 60 | +In this situation, use the `--broken-code` option with `cargo fix` to tell Cargo not to back out the changes. |
| 61 | +Then, you can go manually inspect the error and investigate what is needed to fix it. |
| 62 | + |
| 63 | +Another option to incrementally migrate a project is to apply individual fixes separately, one at a time. |
| 64 | +You can do this by adding the individual lints as warnings, and then either running `cargo fix` (without the `--edition` flag) or using your editor or IDE to apply its suggestions if it supports "Quick Fixes". |
| 65 | + |
| 66 | +For example, the 2018 edition uses the [`keyword-idents`] lint to fix any conflicting keywords. |
| 67 | +You can add `#![warn(keyword_idents)]` to the top of each crate (like at the top of `src/lib.rs` or `src/main.rs`). |
| 68 | +Then, running `cargo fix` will apply just the suggestions for that lint. |
| 69 | + |
| 70 | +You can see the list of lints enabled for each edition in the [lint group] page, or run the `rustc -Whelp` command. |
| 71 | + |
| 72 | +## Migrating macros |
| 73 | + |
| 74 | +Some macros may require manual work to fix them for the next edition. |
| 75 | +For example, `cargo fix --edition` may not be able to automatically fix a macro that generates syntax that does not work in the next edition. |
| 76 | + |
| 77 | +This may be a problem for both [proc macros] and `macro_rules`-style macros. |
| 78 | +`macro_rules` macros can sometimes be automatically updated if the macro is used within the same crate, but there are several situations where it cannot. |
| 79 | +Proc macros in general cannot be automatically fixed at all. |
| 80 | + |
| 81 | +For example, if we migrate a crate containing this (contrived) macro `foo` from 2015 to 2018, `foo` would not be automatically fixed. |
| 82 | + |
| 83 | +```rust |
| 84 | +#[macro_export] |
| 85 | +macro_rules! foo { |
| 86 | + () => { |
| 87 | + let dyn = 1; |
| 88 | + println!("it is {}", dyn); |
| 89 | + }; |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +When this macro is defined in a 2015 crate, it can be used from a crate of any other edition due to macro hygiene (discussed below). |
| 94 | +In 2015, `dyn` is a normal identifier and can be used without restriction. |
| 95 | + |
| 96 | +However, in 2018, `dyn` is no longer a valid identifier. |
| 97 | +When using `cargo fix --edition` to migrate to 2018, Cargo won't display any warnings or errors at all. |
| 98 | +However, `foo` won't work when called from any crate. |
| 99 | + |
| 100 | +If you have macros, you are encouraged to make sure you have tests that fully cover the macro's syntax. |
| 101 | +You may also want to test the macros by importing and using them in crates from multiple editions, just to ensure it works correctly everywhere. |
| 102 | +If you run into issues, you'll need to read through the chapters of this guide to understand how the code can be changed to work across all editions. |
| 103 | + |
| 104 | +### Macro hygiene |
| 105 | + |
| 106 | +Macros use a system called "edition hygiene" where the tokens within a macro are marked with which edition they come from. |
| 107 | +This allows external macros to be called from crates of varying editions without needing to worry about which edition it is called from. |
| 108 | + |
| 109 | +Let's take a closer look at the example above that defines a `macro_rules` macro using `dyn` as an identifier. |
| 110 | +If that macro was defined in a crate using the 2015 edition, then that macro works fine, even if it were called from a 2018 crate where `dyn` is a keyword and that would normally be a syntax error. |
| 111 | +The `let dyn = 1;` tokens are marked as being from 2015, and the compiler will remember that wherever that code gets expanded. |
| 112 | +The parser looks at the edition of the tokens to know how to interpret it. |
| 113 | + |
| 114 | +The problem arises when changing the edition to 2018 in the crate where it is defined. |
| 115 | +Now, those tokens are tagged with the 2018 edition, and those will fail to parse. |
| 116 | +However, since we never called the macro from our crate, `cargo fix --edition` never had a chance to inspect the macro and fix it. |
| 117 | + |
| 118 | +<!-- TODO: hopefully someday, the reference will have chapters on how expansion works, and this can link there for actual details. --> |
| 119 | + |
| 120 | +## Documentation tests |
| 121 | + |
| 122 | +At this time, `cargo fix` is not able to update [documentation tests]. |
| 123 | +After updating the edition in `Cargo.toml`, you should run `cargo test` to ensure everything still passes. |
| 124 | +If your documentation tests use syntax that is not supported in the new edition, you will need to update them manually. |
| 125 | + |
| 126 | +In rare cases, you can manually set the edition for each test. |
| 127 | +For example, you can use the [`edition2018` annotation][rustdoc-annotation] on the triple backticks to tell `rustdoc` which edition to use. |
| 128 | + |
| 129 | +## Generated code |
| 130 | + |
| 131 | +Another area where the automated fixes cannot apply is if you have a build script which generates Rust code at compile time (see [Code generation] for an example). |
| 132 | +In this situation, if you end up with code that doesn't work in the next edition, you will need to manually change the build script to generate code that is compatible. |
| 133 | + |
| 134 | +## Migrating non-Cargo projects |
| 135 | + |
| 136 | +If your project is not using Cargo as a build system, it may still be possible to make use of the automated lints to assist migrating to the next edition. |
| 137 | +You can enable the migration lints as described above by enabling the appropriate [lint group]. |
| 138 | +For example, you can use the `#![warn(rust_2021_compatibility)]` attribute or the `-Wrust-2021-compatibility` or `--force-warns=rust-2021-compatibility` [CLI flag]. |
| 139 | + |
| 140 | +The next step is to apply those lints to your code. |
| 141 | +There are several options here: |
| 142 | + |
| 143 | +* Manually read the warnings and apply the suggestions recommended by the compiler. |
| 144 | +* Use an editor or IDE that supports automatically applying suggestions. |
| 145 | + For example, [Visual Studio Code] with the [Rust Analyzer extension] has the ability to use the "Quick Fix" links to automatically apply suggestions. |
| 146 | + Many other editors and IDEs have similar functionality. |
| 147 | +* Write a migration tool using the [`rustfix`] library. |
| 148 | + This is the library that Cargo uses internally to take the [JSON messages] from the compiler and modify the source code. |
| 149 | + Check the [`examples` directory][rustfix-examples] for examples of how to use the library. |
| 150 | + |
| 151 | +## Writing idiomatic code in a new edition |
| 152 | + |
| 153 | +Editions are not only about new features and removing old ones. |
| 154 | +In any programming language, idioms change over time, and Rust is no exception. |
| 155 | +While old code will continue to compile, it might be written with different idioms today. |
| 156 | + |
| 157 | +For example, in Rust 2015, external crates must be listed with `extern crate` like this: |
| 158 | + |
| 159 | +```rust,ignore |
| 160 | +// src/lib.rs |
| 161 | +extern crate rand; |
| 162 | +``` |
| 163 | + |
| 164 | +In Rust 2018, it is [no longer necessary](../rust-2018/path-changes.md#no-more-extern-crate) to include these items. |
| 165 | + |
| 166 | +`cargo fix` has the `--edition-idioms` option to automatically transition some of these idioms to the new syntax. |
| 167 | + |
| 168 | +> **Warning**: The current *"idiom lints"* are known to have some problems. |
| 169 | +> They may make incorrect suggestions which may fail to compile. |
| 170 | +> The current lints are: |
| 171 | +> * Edition 2018: |
| 172 | +> * [`unused-extern-crates`] |
| 173 | +> * [`explicit-outlives-requirements`] |
| 174 | +> * Edition 2021 does not have any idiom lints. |
| 175 | +> |
| 176 | +> The following instructions are recommended only for the intrepid who are willing to work through a few compiler/Cargo bugs! |
| 177 | +> If you run into problems, you can try the `--broken-code` option [described above](#partial-migration-with-broken-code) to make as much progress as possible, and then resolve the remaining issues manually. |
| 178 | +
|
| 179 | +With that out of the way, we can instruct Cargo to fix our code snippet with: |
| 180 | + |
| 181 | +```console |
| 182 | +cargo fix --edition-idioms |
| 183 | +``` |
| 184 | + |
| 185 | +Afterwards, the line with `extern crate rand;` in `src/lib.rs` will be removed. |
| 186 | + |
| 187 | +We're now more idiomatic, and we didn't have to fix our code manually! |
| 188 | + |
| 189 | +[`cargo check`]: ../../cargo/commands/cargo-check.html |
| 190 | +[`cargo fix`]: ../../cargo/commands/cargo-fix.html |
| 191 | +[`explicit-outlives-requirements`]: ../../rustc/lints/listing/allowed-by-default.html#explicit-outlives-requirements |
| 192 | +[`keyword-idents`]: ../../rustc/lints/listing/allowed-by-default.html#keyword-idents |
| 193 | +[`rustfix`]: https://github.com/rust-lang/rustfix |
| 194 | +[`unused-extern-crates`]: ../../rustc/lints/listing/allowed-by-default.html#unused-extern-crates |
| 195 | +[Cargo features]: ../../cargo/reference/features.html |
| 196 | +[Cargo package]: ../../cargo/reference/manifest.html#the-package-section |
| 197 | +[Cargo targets]: ../../cargo/reference/cargo-targets.html |
| 198 | +[Cargo workspace]: ../../cargo/reference/workspaces.html |
| 199 | +[CLI flag]: ../../rustc/lints/levels.html#via-compiler-flag |
| 200 | +[Code generation]: ../../cargo/reference/build-script-examples.html#code-generation |
| 201 | +[conditional compilation]: ../../reference/conditional-compilation.html |
| 202 | +[documentation tests]: ../../rustdoc/documentation-tests.html |
| 203 | +[JSON messages]: ../../rustc/json.html |
| 204 | +[lint group]: ../../rustc/lints/groups.html |
| 205 | +[lints]: ../../rustc/lints/index.html |
| 206 | +[proc macros]: ../../reference/procedural-macros.html |
| 207 | +[Rust Analyzer extension]: https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer |
| 208 | +[rustdoc-annotation]: ../../rustdoc/documentation-tests.html#attributes |
| 209 | +[rustfix-examples]: https://github.com/rust-lang/rustfix/tree/master/examples |
| 210 | +[Visual Studio Code]: https://code.visualstudio.com/ |
0 commit comments