diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 2eae0132..6804ac2d 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -7,6 +7,7 @@ - [What are editions?](editions/index.md) - [Creating a new project](editions/creating-a-new-project.md) - [Transitioning an existing project to a new edition](editions/transitioning-an-existing-project-to-a-new-edition.md) + - [Advanced migrations](editions/advanced-migrations.md) ## Rust 2015 diff --git a/src/editions/advanced-migrations.md b/src/editions/advanced-migrations.md new file mode 100644 index 00000000..b804ae64 --- /dev/null +++ b/src/editions/advanced-migrations.md @@ -0,0 +1,210 @@ +# Advanced migration strategies + +## How migrations work + +[`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. +These lints include instructions on how to modify the code to make it compatible on both the current and the next edition. +`cargo fix` applies these changes to the source code, and then runs `cargo check` again to verify that the fixes work. +If the fixes fail, then it will back out the changes and display a warning. + +Changing the code to be simultaneously compatible with both the current and next edition makes it easier to incrementally migrate the code. +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. + +The lints that `cargo fix --edition` apply are part of a [lint group]. +For example, when migrating from 2018 to 2021, Cargo uses the `rust-2021-compatibility` group of lints to fix the code. +Check the [Partial migration](#partial-migration-with-broken-code) section below for tips on using individual lints to help with migration. + +`cargo fix` may run `cargo check` multiple times. +For example, after applying one set of fixes, this may trigger new warnings which require further fixes. +Cargo repeats this until no new warnings are generated. + +## Migrating multiple configurations + +`cargo fix` can only work with a single configuration at a time. +If you use [Cargo features] or [conditional compilation], then you may need to run `cargo fix` multiple times with different flags. + +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. +This may require moving your code between machines if you don't have cross-compiling available. + +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. +If you want to migrate feature code individually, you can use the `--features` flag to migrate one at a time. + +## Migrating a large project or workspace + +You can migrate a large project incrementally to make the process easier if you run into problems. + +In a [Cargo workspace], each package defines its own edition, so the process naturally involves migrating one package at a time. + +Within a [Cargo package], you can either migrate the entire package at once, or migrate individual [Cargo targets] one at a time. +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. +By default, `cargo fix` uses `--all-targets`. + +For even more advanced cases, you can specify the edition for each individual target in `Cargo.toml` like this: + +```toml +[[bin]] +name = "my-binary" +edition = "2018" +``` + +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. + +## Partial migration with broken code + +Sometimes the fixes suggested by the compiler may fail to work. +When this happens, Cargo will report a warning indicating what happened and what the error was. +However, by default it will automatically back out the changes it made. +It can be helpful to keep the code in the broken state and manually resolve the issue. +Some of the fixes may have been correct, and the broken fix maybe be *mostly* correct, but just need minor tweaking. + +In this situation, use the `--broken-code` option with `cargo fix` to tell Cargo not to back out the changes. +Then, you can go manually inspect the error and investigate what is needed to fix it. + +Another option to incrementally migrate a project is to apply individual fixes separately, one at a time. +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". + +For example, the 2018 edition uses the [`keyword-idents`] lint to fix any conflicting keywords. +You can add `#![warn(keyword_idents)]` to the top of each crate (like at the top of `src/lib.rs` or `src/main.rs`). +Then, running `cargo fix` will apply just the suggestions for that lint. + +You can see the list of lints enabled for each edition in the [lint group] page, or run the `rustc -Whelp` command. + +## Migrating macros + +Some macros may require manual work to fix them for the next edition. +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. + +This may be a problem for both [proc macros] and `macro_rules`-style macros. +`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. +Proc macros in general cannot be automatically fixed at all. + +For example, if we migrate a crate containing this (contrived) macro `foo` from 2015 to 2018, `foo` would not be automatically fixed. + +```rust +#[macro_export] +macro_rules! foo { + () => { + let dyn = 1; + println!("it is {}", dyn); + }; +} +``` + +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). +In 2015, `dyn` is a normal identifier and can be used without restriction. + +However, in 2018, `dyn` is no longer a valid identifier. +When using `cargo fix --edition` to migrate to 2018, Cargo won't display any warnings or errors at all. +However, `foo` won't work when called from any crate. + +If you have macros, you are encouraged to make sure you have tests that fully cover the macro's syntax. +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. +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. + +### Macro hygiene + +Macros use a system called "edition hygiene" where the tokens within a macro are marked with which edition they come from. +This allows external macros to be called from crates of varying editions without needing to worry about which edition it is called from. + +Let's take a closer look at the example above that defines a `macro_rules` macro using `dyn` as an identifier. +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. +The `let dyn = 1;` tokens are marked as being from 2015, and the compiler will remember that wherever that code gets expanded. +The parser looks at the edition of the tokens to know how to interpret it. + +The problem arises when changing the edition to 2018 in the crate where it is defined. +Now, those tokens are tagged with the 2018 edition, and those will fail to parse. +However, since we never called the macro from our crate, `cargo fix --edition` never had a chance to inspect the macro and fix it. + + + +## Documentation tests + +At this time, `cargo fix` is not able to update [documentation tests]. +After updating the edition in `Cargo.toml`, you should run `cargo test` to ensure everything still passes. +If your documentation tests use syntax that is not supported in the new edition, you will need to update them manually. + +In rare cases, you can manually set the edition for each test. +For example, you can use the [`edition2018` annotation][rustdoc-annotation] on the triple backticks to tell `rustdoc` which edition to use. + +## Generated code + +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). +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. + +## Migrating non-Cargo projects + +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. +You can enable the migration lints as described above by enabling the appropriate [lint group]. +For example, you can use the `#![warn(rust_2021_compatibility)]` attribute or the `-Wrust-2021-compatibility` or `--force-warns=rust-2021-compatibility` [CLI flag]. + +The next step is to apply those lints to your code. +There are several options here: + +* Manually read the warnings and apply the suggestions recommended by the compiler. +* Use an editor or IDE that supports automatically applying suggestions. + For example, [Visual Studio Code] with the [Rust Analyzer extension] has the ability to use the "Quick Fix" links to automatically apply suggestions. + Many other editors and IDEs have similar functionality. +* Write a migration tool using the [`rustfix`] library. + This is the library that Cargo uses internally to take the [JSON messages] from the compiler and modify the source code. + Check the [`examples` directory][rustfix-examples] for examples of how to use the library. + +## Writing idiomatic code in a new edition + +Editions are not only about new features and removing old ones. +In any programming language, idioms change over time, and Rust is no exception. +While old code will continue to compile, it might be written with different idioms today. + +For example, in Rust 2015, external crates must be listed with `extern crate` like this: + +```rust,ignore +// src/lib.rs +extern crate rand; +``` + +In Rust 2018, it is [no longer necessary](../rust-2018/path-changes.md#no-more-extern-crate) to include these items. + +`cargo fix` has the `--edition-idioms` option to automatically transition some of these idioms to the new syntax. + +> **Warning**: The current *"idiom lints"* are known to have some problems. +> They may make incorrect suggestions which may fail to compile. +> The current lints are: +> * Edition 2018: +> * [`unused-extern-crates`] +> * [`explicit-outlives-requirements`] +> * Edition 2021 does not have any idiom lints. +> +> The following instructions are recommended only for the intrepid who are willing to work through a few compiler/Cargo bugs! +> 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. + +With that out of the way, we can instruct Cargo to fix our code snippet with: + +```console +cargo fix --edition-idioms +``` + +Afterwards, the line with `extern crate rand;` in `src/lib.rs` will be removed. + +We're now more idiomatic, and we didn't have to fix our code manually! + +[`cargo check`]: ../../cargo/commands/cargo-check.html +[`cargo fix`]: ../../cargo/commands/cargo-fix.html +[`explicit-outlives-requirements`]: ../../rustc/lints/listing/allowed-by-default.html#explicit-outlives-requirements +[`keyword-idents`]: ../../rustc/lints/listing/allowed-by-default.html#keyword-idents +[`rustfix`]: https://github.com/rust-lang/rustfix +[`unused-extern-crates`]: ../../rustc/lints/listing/allowed-by-default.html#unused-extern-crates +[Cargo features]: ../../cargo/reference/features.html +[Cargo package]: ../../cargo/reference/manifest.html#the-package-section +[Cargo targets]: ../../cargo/reference/cargo-targets.html +[Cargo workspace]: ../../cargo/reference/workspaces.html +[CLI flag]: ../../rustc/lints/levels.html#via-compiler-flag +[Code generation]: ../../cargo/reference/build-script-examples.html#code-generation +[conditional compilation]: ../../reference/conditional-compilation.html +[documentation tests]: ../../rustdoc/documentation-tests.html +[JSON messages]: ../../rustc/json.html +[lint group]: ../../rustc/lints/groups.html +[lints]: ../../rustc/lints/index.html +[proc macros]: ../../reference/procedural-macros.html +[Rust Analyzer extension]: https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer +[rustdoc-annotation]: ../../rustdoc/documentation-tests.html#attributes +[rustfix-examples]: https://github.com/rust-lang/rustfix/tree/master/examples +[Visual Studio Code]: https://code.visualstudio.com/ diff --git a/src/editions/transitioning-an-existing-project-to-a-new-edition.md b/src/editions/transitioning-an-existing-project-to-a-new-edition.md index beb23604..6732837e 100644 --- a/src/editions/transitioning-an-existing-project-to-a-new-edition.md +++ b/src/editions/transitioning-an-existing-project-to-a-new-edition.md @@ -1,37 +1,54 @@ # Transitioning an existing project to a new edition -New editions might change the way you write Rust – they add new syntax, -language, and library features, and also remove features. For example, `try`, -`async`, and `await` are keywords in Rust 2018, but not Rust 2015. If you -have a project that's using Rust 2015, and you'd like to use Rust 2018 for it -instead, there's a few steps that you need to take. +Rust includes tooling to automatically transition a project from one edition to the next. +It will update your source code so that it is compatible with the next edition. +Briefly, the steps to update to the next edition are: + +1. Run `cargo fix --edition` +2. Edit `Cargo.toml` and set the `edition` field to the next edition, for example `edition = "2021"` +3. Run `cargo build` or `cargo test` to verify the fixes worked. + + +> 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]. +> The steps to follow are: +> +> 1. Install the most recent nightly: `rustup update nightly`. +> 2. Run `cargo +nightly fix --edition`. +> 3. Edit `Cargo.toml` and place `cargo-features = ["edition2021"]` at the top (above `[package]`), and change the edition field to say `edition = "2021"`. +> 4. Run `cargo +nightly check` to verify it now works in the new edition. + +The following sections dig into the details of these steps, and some of the issues you may encounter along the way. > It's our intention that the migration to new editions is as smooth an > experience as possible. If it's difficult for you to upgrade to the latest edition, > we consider that a bug. If you run into problems with this process, please -> [file a bug](https://github.com/rust-lang/rust/issues/new). Thank you! +> [file a bug](https://github.com/rust-lang/rust/issues/new/choose). Thank you! + +## Starting the migration + +As an example, let's take a look at transitioning from the 2015 edition to the 2018 edition. +The steps are essentially the same when transitioning to other editions like 2021. -Here's an example. Imagine we have a crate that has this code in -`src/lib.rs`: +Imagine we have a crate that has this code in `src/lib.rs`: ```rust trait Foo { - fn foo(&self, Box); + fn foo(&self, i32); } ``` -This code uses an anonymous parameter, that `Box`. This is [not +This code uses an anonymous parameter, that `i32`. This is [not supported in Rust 2018](../rust-2018/trait-system/no-anon-params.md), and so this would fail to compile. Let's get this code up to date! ## Updating your code to be compatible with the new edition -Your code may or may not use features that are incompatible with the new -edition. In order to help transition to Rust 2018, we've included a new -subcommand with Cargo. To start, let's run it: +Your code may or may not use features that are incompatible with the new edition. +In order to help transition to the next edition, Cargo includes the [`cargo fix`] subcommand to automatically update your source code. +To start, let's run it: ```console -> cargo fix --edition +cargo fix --edition ``` This will check your code, and automatically fix any issues that it can. @@ -39,106 +56,43 @@ Let's look at `src/lib.rs` again: ```rust trait Foo { - fn foo(&self, _: Box); + fn foo(&self, _: i32); } ``` -It's re-written our code to introduce a parameter name for that trait object. +It's re-written our code to introduce a parameter name for that `i32` value. In this case, since it had no name, `cargo fix` will replace it with `_`, which is conventional for unused variables. `cargo fix` can't always fix your code automatically. If `cargo fix` can't fix something, it will print the warning that it cannot fix -to the console. If you see one of these warnings, you'll have to update your code -manually. See the corresponding section of this guide for help, and if you have -problems, please seek help at the [user's forums](https://users.rust-lang.org/). - -Keep running `cargo fix --edition` until you have no more warnings. - -Congrats! Your code is now valid in both Rust 2015 and Rust 2018! +to the console. If you see one of these warnings, you'll have to update your code manually. +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. +If you have problems, please seek help at the [user's forums](https://users.rust-lang.org/). ## Enabling the new edition to use new features In order to use some new features, you must explicitly opt in to the new -edition. Once you're ready to commit, change your `Cargo.toml` to add the new +edition. Once you're ready to continue, change your `Cargo.toml` to add the new `edition` key/value pair. For example: ```toml [package] name = "foo" version = "0.1.0" -authors = ["Your Name "] edition = "2018" ``` If there's no `edition` key, Cargo will default to Rust 2015. But in this case, -we've chosen `2018`, and so our code is compiling with Rust 2018! - -## Writing idiomatic code in a new edition - -Editions are not only about new features and removing old ones. In any programming -language, idioms change over time, and Rust is no exception. While old code -will continue to compile, it might be written with different idioms today. - -Our sample code contains an outdated idiom. Here it is again: - -```rust -trait Foo { - fn foo(&self, _: Box); -} -``` - -In Rust 2018, it's considered idiomatic to use the [`dyn` -keyword](../rust-2018/trait-system/dyn-trait-for-trait-objects.md) for -trait objects. - -Eventually, we want `cargo fix` to fix all these idioms automatically in the same -manner we did for upgrading to the 2018 edition. **Currently, -though, the *"idiom lints"* are not ready for widespread automatic fixing.** The -compiler isn't making `cargo fix`-compatible suggestions in many cases right -now, and it is making incorrect suggestions in others. Enabling the idiom lints, -even with `cargo fix`, is likely to leave your crate either broken or with many -warnings still remaining. +we've chosen `2018`, and so our code will compile with Rust 2018! -We have plans to make these idiom migrations a seamless part of the Rust 2018 -experience, but we're not there yet. As a result the following instructions are -recommended only for the intrepid who are willing to work through a few -compiler/Cargo bugs! +The next step is to test your project on the new edition. +Run your project tests to verify that everything still works, such as running [`cargo test`]. +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. -With that out of the way, we can instruct Cargo to fix our code snippet with: - -```console -$ cargo fix --edition-idioms -``` - -Afterwards, `src/lib.rs` looks like this: - -```rust -trait Foo { - fn foo(&self, _: Box); -} -``` - -We're now more idiomatic, and we didn't have to fix our code manually! - -Note that `cargo fix` may still not be able to automatically update our code. -If `cargo fix` can't fix something, it will print a warning to the console, and -you'll have to fix it manually. - -As mentioned before, there are known bugs around the idiom lints which -means they're not all ready for prime time yet. You may get a scary-looking -warning to report a bug to Cargo, which happens whenever a fix proposed by -`rustc` actually caused code to stop compiling by accident. If you'd like `cargo -fix` to make as much progress as possible, even if it causes code to stop -compiling, you can execute: - -```console -$ cargo fix --edition-idioms --broken-code -``` - -This will instruct `cargo fix` to apply automatic suggestions regardless of -whether they work or not. Like usual, you'll see the compilation result after -all fixes are applied. If you notice anything wrong or unusual, please feel free -to report an issue to Cargo and we'll help prioritize and fix it. +Congrats! Your code is now valid in both Rust 2015 and Rust 2018! -Enjoy the new edition! +[`cargo fix`]: ../../cargo/commands/cargo-fix.html +[`cargo test`]: ../../cargo/commands/cargo-test.html +[Advanced migration strategies]: advanced-migrations.md +[nightly channel]: ../../book/appendix-07-nightly-rust.html