Skip to content

Commit 36530e2

Browse files
committed
Update transitioning chapter.
1 parent 288f611 commit 36530e2

File tree

3 files changed

+220
-92
lines changed

3 files changed

+220
-92
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- [What are editions?](editions/index.md)
88
- [Creating a new project](editions/creating-a-new-project.md)
99
- [Transitioning an existing project to a new edition](editions/transitioning-an-existing-project-to-a-new-edition.md)
10+
- [Advanced migrations](editions/advanced-migrations.md)
1011

1112
## Rust 2015
1213

src/editions/advanced-migrations.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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 the fixes work.
8+
If the fixes fail, then it will back out the changes and display a warning.
9+
10+
The lints that `cargo fix --edition` apply are part of a [lint group].
11+
For example, when migrating from 2018 to 2021, Cargo uses the `rust-2021-compatibility` group of lints to fix the code.
12+
Check the [Partial migration](#partial-migration-with-broken-code) section below for tips on using individual lints to help with migration.
13+
14+
`cargo fix` may run `cargo check` multiple times.
15+
For example, after applying one set of fixes, this may trigger new warnings which require further fixes.
16+
Cargo repeats this until no new warnings are generated.
17+
18+
## Migrating multiple configurations
19+
20+
`cargo fix` can only work with a single configuration at a time.
21+
If you use [Cargo features] or [conditional compilation], then you may need to run `cargo fix` multiple times with different flags.
22+
23+
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.
24+
This may require moving your code between machines if you don't have cross-compiling available.
25+
26+
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.
27+
If you want to migrate feature code individually, you can use the `--features` flag to migrate one at a time.
28+
29+
## Migrating a large project or workspace
30+
31+
You can migrate a large project incrementally to make the process easier if you run into problems.
32+
33+
In a [Cargo workspace], each package defines its own edition, so the process naturally involves migrating one package at a time.
34+
35+
Within a [Cargo package], you can either migrate the entire package at once, or migrate individual [Cargo targets] one at a time.
36+
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.
37+
By default, `cargo fix` uses `--all-targets`.
38+
39+
For even more advanced cases, you can specify the edition for each individual target in `Cargo.toml` like this:
40+
41+
```toml
42+
[[bin]]
43+
name = "my-binary"
44+
edition = "2018"
45+
```
46+
47+
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.
48+
49+
## Partial migration with broken code
50+
51+
Sometimes the fixes suggested by the compiler may fail to work.
52+
When this happens, Cargo will report a warning indicating what happened and what the error was.
53+
However, by default it will automatically back out the changes it made.
54+
It can be helpful to keep the code in the broken state and manually resolve the issue.
55+
Some of the fixes may have been correct, and the broken fix maybe be *mostly* correct, but just need minor tweaking.
56+
57+
In this situation, use the `--broken-code` option with `cargo fix` to tell Cargo not to back out the changes.
58+
Then, you can go manually inspect the error and investigate what is needed to fix it.
59+
60+
Another option to incrementally migrate a project is to apply individual fixes separately, one at a time.
61+
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".
62+
63+
For example, the 2018 edition uses the [`keyword-idents`] lint to fix any conflicting keywords.
64+
You can add `#![warn(keyword_idents)]` to the top of each crate (like at the top of `src/lib.rs` or `src/main.rs`).
65+
Then, running `cargo fix` will apply just the suggestions for that lint.
66+
67+
You can see the list of lints enabled for each edition in the [lint group] page, or run the `rustc -Whelp` command.
68+
69+
## Migrating macros
70+
71+
Some macros may require manual work to fix them for the next edition.
72+
For example, a macro that generates syntax that only works on the previous edition, then `cargo fix` will not be able to fix it.
73+
74+
This may be a problem for both [proc macros] and `macro_rules`-style macros.
75+
`macro_rules` macros can be updated if the macro is used within the same crate, but if it is exported with `#[macro_export]`, then it may not be able to fix it.
76+
Proc macros in general cannot be fixed at all.
77+
78+
For example, this (contrived) macro won't get fixed when migrating to 2018.
79+
80+
```rust
81+
#[macro_export]
82+
macro_rules! foo {
83+
() => {
84+
let dyn = 1;
85+
println!("it is {}", dyn);
86+
};
87+
}
88+
```
89+
90+
If you don't have any tests that actually call this macro, then `cargo fix --edition` won't display any warnings or errors at all.
91+
However, it won't work when called from another crate.
92+
93+
If you have proc macros or exported macros, you are encouraged to test them by importing them in crates from multiple editions.
94+
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.
95+
96+
### Macro hygiene
97+
98+
Macros use a system called "edition hygiene" where the tokens within a macro are marked with which edition they come from.
99+
This allows external macros to be called from crates of varying editions without needing to worry about which edition it is called from.
100+
101+
Let's take a closer look at the example above that defines a `macro_rules` macro using `dyn` as an identifier.
102+
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.
103+
The `let dyn = 1;` tokens are marked as being from 2015, and the compiler will remember that wherever that code gets expanded.
104+
The parser looks at the edition of the tokens to know how to interpret it.
105+
106+
The problem arises when changing the edition to 2018 in the crate where it is defined.
107+
Now, those tokens are tagged with the 2018 edition, and those will fail to parse.
108+
However, since we never called the macro from our crate, `cargo fix --edition` never had a chance to inspect the macro and fix it.
109+
110+
<!-- TODO: hopefully someday, the reference will have chapters on how expansion works, and this can link there for actual details. -->
111+
112+
## Documentation tests
113+
114+
At this time, `cargo fix` is not able to update [documentation tests].
115+
After updating the edition in `Cargo.toml`, you should run `cargo test` to ensure everything still passes.
116+
If your documentation tests use syntax that is not supported in the new edition, you will need to update them manually.
117+
118+
In rare cases, you can manually set the edition for each test.
119+
For example, you can use the [`edition2018` annotation][rustdoc-annotation] on the triple backticks to tell `rustdoc` which edition to use.
120+
121+
## Writing idiomatic code in a new edition
122+
123+
Editions are not only about new features and removing old ones.
124+
In any programming language, idioms change over time, and Rust is no exception.
125+
While old code will continue to compile, it might be written with different idioms today.
126+
127+
For example, in Rust 2015, external crates must be listed with `extern crate` like this:
128+
129+
```rust,ignore
130+
// src/lib.rs
131+
extern crate rand;
132+
```
133+
134+
In Rust 2018, it is [no longer necessary](../rust-2018/path-changes.md#no-more-extern-crate) to include these items.
135+
136+
`cargo fix` has the `--edition-idioms` option to automatically transition some of these idioms to the new syntax.
137+
138+
> **Warning**: The current *"idiom lints"* are known to have some problems.
139+
> They may make incorrect suggestions which may fail to compile.
140+
> The current lints are:
141+
> * Edition 2018:
142+
> * [`unused-extern-crates`]
143+
> * [`explicit-outlives-requirements`]
144+
> * Edition 2021 does not have any idiom lints.
145+
>
146+
> The following instructions are recommended only for the intrepid who are willing to work through a few compiler/Cargo bugs!
147+
> 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.
148+
149+
With that out of the way, we can instruct Cargo to fix our code snippet with:
150+
151+
```console
152+
cargo fix --edition-idioms
153+
```
154+
155+
Afterwards, the line with `extern crate rand;` in `src/lib.rs` will be removed.
156+
157+
We're now more idiomatic, and we didn't have to fix our code manually!
158+
159+
[`cargo check`]: ../../cargo/commands/cargo-check.html
160+
[`cargo fix`]: ../../cargo/commands/cargo-fix.html
161+
[`explicit-outlives-requirements`]: ../../rustc/lints/listing/allowed-by-default.html#explicit-outlives-requirements
162+
[`keyword-idents`]: ../../rustc/lints/listing/allowed-by-default.html#keyword-idents
163+
[`unused-extern-crates`]: ../../rustc/lints/listing/allowed-by-default.html#unused-extern-crates
164+
[Cargo features]: ../../cargo/reference/features.html
165+
[Cargo package]: ../../cargo/reference/manifest.html#the-package-section
166+
[Cargo targets]: ../../cargo/reference/cargo-targets.html
167+
[Cargo workspace]: ../../cargo/reference/workspaces.html
168+
[conditional compilation]: ../../reference/conditional-compilation.html
169+
[documentation tests]: ../../rustdoc/documentation-tests.html
170+
[lint group]: ../../rustc/lints/groups.html
171+
[lints]: ../../rustc/lints/index.html
172+
[proc macros]: ../../reference/procedural-macros.html
173+
[rustdoc-annotation]: ../../rustdoc/documentation-tests.html#attributes
Lines changed: 46 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,98 @@
11
# Transitioning an existing project to a new edition
22

3-
New editions might change the way you write Rust – they add new syntax,
4-
language, and library features, and also remove features. For example, `try`,
5-
`async`, and `await` are keywords in Rust 2018, but not Rust 2015. If you
6-
have a project that's using Rust 2015, and you'd like to use Rust 2018 for it
7-
instead, there's a few steps that you need to take.
3+
Rust includes tooling to automatically transition a project from one edition to the next.
4+
It will update your source code so that it is compatible with the next edition.
5+
Briefly, the steps to update to the next edition are:
6+
7+
1. Run `cargo fix --edition`
8+
2. Edit `Cargo.toml` and set the `edition` field to the next edition, for example `edition = "2021"`
9+
3. Run `cargo build` or `cargo test` to verify the fixes worked.
10+
11+
<!-- remove this when 2021 is stabilized -->
12+
> If you are migrating from 2018 to 2021, the steps are slightly different because 2021 is not yet stabilized, and is only available on the [nightly channel].
13+
> The steps to follow are:
14+
>
15+
> 1. Install the most recent nightly: `rustup update nightly`.
16+
> 2. Run `cargo +nightly fix --edition`.
17+
> 3. Edit `Cargo.toml` and place `cargo-features = ["edition2021"]` at the top (above `[package]`), and change the edition field to say `edition = "2021"`.
18+
> 4. Run `cargo +nightly check` to verify it now works in the new edition.
19+
20+
The following sections dig into the details of these steps, and some of the issues you may encounter along the way.
821

922
> It's our intention that the migration to new editions is as smooth an
1023
> experience as possible. If it's difficult for you to upgrade to the latest edition,
1124
> we consider that a bug. If you run into problems with this process, please
12-
> [file a bug](https://github.com/rust-lang/rust/issues/new). Thank you!
25+
> [file a bug](https://github.com/rust-lang/rust/issues/new/choose). Thank you!
26+
27+
## Starting the migration
28+
29+
As an example, let's take a look at transitioning from the 2015 edition to the 2018 edition.
30+
The steps are essentially the same when transitioning to other editions like 2021.
1331

14-
Here's an example. Imagine we have a crate that has this code in
15-
`src/lib.rs`:
32+
Imagine we have a crate that has this code in `src/lib.rs`:
1633

1734
```rust
1835
trait Foo {
19-
fn foo(&self, Box<Foo>);
36+
fn foo(&self, i32);
2037
}
2138
```
2239

23-
This code uses an anonymous parameter, that `Box<Foo>`. This is [not
40+
This code uses an anonymous parameter, that `i32`. This is [not
2441
supported in Rust 2018](../rust-2018/trait-system/no-anon-params.md), and
2542
so this would fail to compile. Let's get this code up to date!
2643

2744
## Updating your code to be compatible with the new edition
2845

29-
Your code may or may not use features that are incompatible with the new
30-
edition. In order to help transition to Rust 2018, we've included a new
31-
subcommand with Cargo. To start, let's run it:
46+
Your code may or may not use features that are incompatible with the new edition.
47+
In order to help transition to the next edition, Cargo includes the [`cargo fix`] subcommand to automatically update your source code.
48+
To start, let's run it:
3249

3350
```console
34-
> cargo fix --edition
51+
cargo fix --edition
3552
```
3653

3754
This will check your code, and automatically fix any issues that it can.
3855
Let's look at `src/lib.rs` again:
3956

4057
```rust
4158
trait Foo {
42-
fn foo(&self, _: Box<Foo>);
59+
fn foo(&self, _: i32);
4360
}
4461
```
4562

46-
It's re-written our code to introduce a parameter name for that trait object.
63+
It's re-written our code to introduce a parameter name for that `i32` value.
4764
In this case, since it had no name, `cargo fix` will replace it with `_`,
4865
which is conventional for unused variables.
4966

5067
`cargo fix` can't always fix your code automatically.
5168
If `cargo fix` can't fix something, it will print the warning that it cannot fix
52-
to the console. If you see one of these warnings, you'll have to update your code
53-
manually. See the corresponding section of this guide for help, and if you have
54-
problems, please seek help at the [user's forums](https://users.rust-lang.org/).
55-
56-
Keep running `cargo fix --edition` until you have no more warnings.
57-
58-
Congrats! Your code is now valid in both Rust 2015 and Rust 2018!
69+
to the console. If you see one of these warnings, you'll have to update your code manually.
70+
See the [Advanced migration strategies] chapter for more on working with the migration process, and read the chapters in this guide which explain which changes are needed.
71+
If you have problems, please seek help at the [user's forums](https://users.rust-lang.org/).
5972

6073
## Enabling the new edition to use new features
6174

6275
In order to use some new features, you must explicitly opt in to the new
63-
edition. Once you're ready to commit, change your `Cargo.toml` to add the new
76+
edition. Once you're ready to continue, change your `Cargo.toml` to add the new
6477
`edition` key/value pair. For example:
6578

6679
```toml
6780
[package]
6881
name = "foo"
6982
version = "0.1.0"
70-
authors = ["Your Name <you@example.com>"]
7183
edition = "2018"
7284
```
7385

7486
If there's no `edition` key, Cargo will default to Rust 2015. But in this case,
75-
we've chosen `2018`, and so our code is compiling with Rust 2018!
76-
77-
## Writing idiomatic code in a new edition
78-
79-
Editions are not only about new features and removing old ones. In any programming
80-
language, idioms change over time, and Rust is no exception. While old code
81-
will continue to compile, it might be written with different idioms today.
82-
83-
Our sample code contains an outdated idiom. Here it is again:
84-
85-
```rust
86-
trait Foo {
87-
fn foo(&self, _: Box<Foo>);
88-
}
89-
```
90-
91-
In Rust 2018, it's considered idiomatic to use the [`dyn`
92-
keyword](../rust-2018/trait-system/dyn-trait-for-trait-objects.md) for
93-
trait objects.
94-
95-
Eventually, we want `cargo fix` to fix all these idioms automatically in the same
96-
manner we did for upgrading to the 2018 edition. **Currently,
97-
though, the *"idiom lints"* are not ready for widespread automatic fixing.** The
98-
compiler isn't making `cargo fix`-compatible suggestions in many cases right
99-
now, and it is making incorrect suggestions in others. Enabling the idiom lints,
100-
even with `cargo fix`, is likely to leave your crate either broken or with many
101-
warnings still remaining.
87+
we've chosen `2018`, and so our code will compile with Rust 2018!
10288

103-
We have plans to make these idiom migrations a seamless part of the Rust 2018
104-
experience, but we're not there yet. As a result the following instructions are
105-
recommended only for the intrepid who are willing to work through a few
106-
compiler/Cargo bugs!
89+
The next step is to test your project on the new edition.
90+
Run your project tests to verify that everything still works, such as running [`cargo test`].
91+
If new warnings are issued, you may want to consider running `cargo fix` again (without the `--edition` flag) to apply any suggestions given by the compiler.
10792

108-
With that out of the way, we can instruct Cargo to fix our code snippet with:
109-
110-
```console
111-
$ cargo fix --edition-idioms
112-
```
113-
114-
Afterwards, `src/lib.rs` looks like this:
115-
116-
```rust
117-
trait Foo {
118-
fn foo(&self, _: Box<dyn Foo>);
119-
}
120-
```
121-
122-
We're now more idiomatic, and we didn't have to fix our code manually!
123-
124-
Note that `cargo fix` may still not be able to automatically update our code.
125-
If `cargo fix` can't fix something, it will print a warning to the console, and
126-
you'll have to fix it manually.
127-
128-
As mentioned before, there are known bugs around the idiom lints which
129-
means they're not all ready for prime time yet. You may get a scary-looking
130-
warning to report a bug to Cargo, which happens whenever a fix proposed by
131-
`rustc` actually caused code to stop compiling by accident. If you'd like `cargo
132-
fix` to make as much progress as possible, even if it causes code to stop
133-
compiling, you can execute:
134-
135-
```console
136-
$ cargo fix --edition-idioms --broken-code
137-
```
138-
139-
This will instruct `cargo fix` to apply automatic suggestions regardless of
140-
whether they work or not. Like usual, you'll see the compilation result after
141-
all fixes are applied. If you notice anything wrong or unusual, please feel free
142-
to report an issue to Cargo and we'll help prioritize and fix it.
93+
Congrats! Your code is now valid in both Rust 2015 and Rust 2018!
14394

144-
Enjoy the new edition!
95+
[`cargo fix`]: ../../cargo/commands/cargo-fix.html
96+
[`cargo test`]: ../../cargo/commands/cargo-test.html
97+
[Advanced migration strategies]: advanced-migrations.md
98+
[nightly channel]: ../../book/appendix-07-nightly-rust.html

0 commit comments

Comments
 (0)