| 
 | 1 | +# Editions  | 
 | 2 | + | 
 | 3 | +<!-- toc -->  | 
 | 4 | + | 
 | 5 | +This chapter gives an overview of how Edition support works in rustc.  | 
 | 6 | +This assumes that you are familiar with what Editions are (see the [Edition Guide]).  | 
 | 7 | + | 
 | 8 | +[Edition Guide]: https://doc.rust-lang.org/edition-guide/  | 
 | 9 | + | 
 | 10 | +## Edition definition  | 
 | 11 | + | 
 | 12 | +The `--edition` CLI flag specifies the edition to use for a crate.  | 
 | 13 | +This can be accessed from [`Session::edition`].  | 
 | 14 | +There are convenience functions like [`Session::at_least_rust_2021`] for checking the crate's  | 
 | 15 | +edition, though you should be careful about whether you check the global session or the span, see  | 
 | 16 | +[Edition hygiene] below.  | 
 | 17 | + | 
 | 18 | +As an alternative to the `at_least_rust_20xx` convenience methods, the [`Edition`] type also  | 
 | 19 | +supports comparisons for doing range checks, such as `span.edition() >= Edition::Edition2021`.  | 
 | 20 | + | 
 | 21 | +[`Session::edition`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/struct.Session.html#method.edition  | 
 | 22 | +[`Session::at_least_rust_2021`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/struct.Session.html#method.at_least_rust_2021  | 
 | 23 | +[`Edition`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/edition/enum.Edition.html  | 
 | 24 | + | 
 | 25 | +### Adding a new edition  | 
 | 26 | + | 
 | 27 | +Adding a new edition mainly involves adding a variant to the [`Edition`] enum and then fixing  | 
 | 28 | +everything that is broken. See [#94461](https://github.com/rust-lang/rust/pull/94461) for an  | 
 | 29 | +example.  | 
 | 30 | + | 
 | 31 | +### Features and Edition stability  | 
 | 32 | + | 
 | 33 | +The [`Edition`] enum defines whether or not an edition is stable.  | 
 | 34 | +If it is not stable, then the `-Zunstable-options` CLI option must be passed to enable it.  | 
 | 35 | + | 
 | 36 | +When adding a new feature, there are two options you can choose for how to handle stability with a  | 
 | 37 | +future edition:  | 
 | 38 | + | 
 | 39 | +- Just check the edition of the span like `span.at_least_rust_20xx()` (see [Edition hygiene]) or the  | 
 | 40 | +  [`Session::edition`]. This will implicitly depend on the stability of the edition itself to  | 
 | 41 | +  indicate that your feature is available.  | 
 | 42 | +- Place your new behavior behind a [feature gate].  | 
 | 43 | + | 
 | 44 | +It may be sufficient to only check the current edition for relatively simple changes.  | 
 | 45 | +However, for larger language changes, you should consider creating a feature gate.  | 
 | 46 | +There are several benefits to using a feature gate:  | 
 | 47 | + | 
 | 48 | +- A feature gate makes it easier to work on and experiment with a new feature.  | 
 | 49 | +- It makes the intent clear when the `#![feature(…)]` attribute is used that your new feature is  | 
 | 50 | +  being enabled.  | 
 | 51 | +- It makes testing of editions easier so that features that are not yet complete do not interfere  | 
 | 52 | +  with testing of edition-specific features that are complete and ready.  | 
 | 53 | +- It decouples the feature from an edition, which makes it easier for the team to make a deliberate  | 
 | 54 | +  decision of whether or not a feature should be added to the next edition when the feature is  | 
 | 55 | +  ready.  | 
 | 56 | + | 
 | 57 | +When a feature is complete and ready, the feature gate can be removed (and the code should just  | 
 | 58 | +check the span or `Session` edition to determine if it is enabled).  | 
 | 59 | + | 
 | 60 | +There are a few different options for doing feature checks:  | 
 | 61 | + | 
 | 62 | +- For highly experimental features, that may or may not be involved in an edition, they can  | 
 | 63 | +  implement regular feature gates like `tcx.features().my_feature`, and ignore editions for the time  | 
 | 64 | +  being.  | 
 | 65 | + | 
 | 66 | +- For experimental features that *might* be involved in an edition, they should implement gates with  | 
 | 67 | +  `tcx.features().my_feature && span.at_least_rust_20xx()`.  | 
 | 68 | +  This requires the user to still specify `#![feature(my_feature)]`, to avoid disrupting testing of  | 
 | 69 | +  other edition features which are ready and have been accepted within the edition.  | 
 | 70 | + | 
 | 71 | +- For experimental features that have graduated to definitely be part of an edition,  | 
 | 72 | +  they should implement gates with `tcx.features().my_feature || span.at_least_rust_20xx()`,  | 
 | 73 | +  or just remove the feature check altogether and just check `span.at_least_rust_20xx()`.  | 
 | 74 | + | 
 | 75 | +If you need to do the feature gating in multiple places, consider placing the check in a single  | 
 | 76 | +function so that there will only be a single place to update. For example:  | 
 | 77 | + | 
 | 78 | +```rust,ignore  | 
 | 79 | +// An example from Edition 2021 disjoint closure captures.  | 
 | 80 | +
  | 
 | 81 | +fn enable_precise_capture(tcx: TyCtxt<'_>, span: Span) -> bool {  | 
 | 82 | +    tcx.features().capture_disjoint_fields || span.rust_2021()  | 
 | 83 | +}  | 
 | 84 | +```  | 
 | 85 | + | 
 | 86 | +See [Lints and stability](#lints-and-stability) below for more information about how lints handle  | 
 | 87 | +stability.  | 
 | 88 | + | 
 | 89 | +[feature gate]: ../feature-gates.md  | 
 | 90 | + | 
 | 91 | +## Edition parsing  | 
 | 92 | + | 
 | 93 | +For the most part, the lexer is edition-agnostic.  | 
 | 94 | +Within [`StringReader`], tokens can be modified based on edition-specific behavior.  | 
 | 95 | +For example, C-String literals like `c"foo"` are split into multiple tokens in editions before 2021.  | 
 | 96 | +This is also where things like reserved prefixes are handled for the 2021 edition.  | 
 | 97 | + | 
 | 98 | +Edition-specific parsing is relatively rare. One example is `async fn` which checks the span of the  | 
 | 99 | +token to determine if it is the 2015 edition, and emits an error in that case.  | 
 | 100 | +This can only be done if the syntax was already invalid.  | 
 | 101 | + | 
 | 102 | +If you need to do edition checking in the parser, you will normally want to look at the edition of  | 
 | 103 | +the token, see [Edition hygiene].  | 
 | 104 | +In some rare cases you may instead need to check the global edition from [`ParseSess::edition`].  | 
 | 105 | + | 
 | 106 | +Most edition-specific parsing behavior is handled with [migration lints] instead of in the parser.  | 
 | 107 | +This is appropriate when there is a *change* in syntax (as opposed to new syntax).  | 
 | 108 | +This allows the old syntax to continue to work on previous editions.  | 
 | 109 | +The lint then checks for the change in behavior.  | 
 | 110 | +On older editions, the lint pass should emit the migration lint to help with migrating to new  | 
 | 111 | +editions.  | 
 | 112 | +On newer editions, your code should emit a hard error with `emit_err` instead.  | 
 | 113 | +For example, the deprecated `start...end` pattern syntax emits the  | 
 | 114 | +[`ellipsis_inclusive_range_patterns`] lint on editions before 2021, and in 2021 is an hard error via  | 
 | 115 | +the `emit_err` method.  | 
 | 116 | + | 
 | 117 | +[`StringReader`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_parse/lexer/struct.StringReader.html  | 
 | 118 | +[`ParseSess::edition`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_session/parse/struct.ParseSess.html#structfield.edition  | 
 | 119 | +[`ellipsis_inclusive_range_patterns`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#ellipsis-inclusive-range-patterns  | 
 | 120 | + | 
 | 121 | +### Keywords  | 
 | 122 | + | 
 | 123 | +New keywords can be introduced across an edition boundary.  | 
 | 124 | +This is implemented by functions like [`Symbol::is_used_keyword_conditional`], which rely on the  | 
 | 125 | +ordering of how the keywords are defined.  | 
 | 126 | + | 
 | 127 | +When new keywords are introduced, the [`keyword_idents`] lint should be updated so that automatic  | 
 | 128 | +migrations can transition code that might be using the keyword as an identifier (see  | 
 | 129 | +[`KeywordIdents`]).  | 
 | 130 | +An alternative to consider is to implement the keyword as a weak keyword if the position it is used  | 
 | 131 | +is sufficient to distinguish it.  | 
 | 132 | + | 
 | 133 | +An additional option to consider is the `k#` prefix which was introduced in [RFC 3101].  | 
 | 134 | +This allows the use of a keyword in editions *before* the edition where the keyword is introduced.  | 
 | 135 | +This is currently not implemented.  | 
 | 136 | + | 
 | 137 | +[`Symbol::is_used_keyword_conditional`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Symbol.html#method.is_used_keyword_conditional  | 
 | 138 | +[`keyword_idents`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/allowed-by-default.html#keyword-idents  | 
 | 139 | +[`KeywordIdents`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/builtin/struct.KeywordIdents.html  | 
 | 140 | +[RFC 3101]: https://rust-lang.github.io/rfcs/3101-reserved_prefixes.html  | 
 | 141 | + | 
 | 142 | +### Edition hygiene  | 
 | 143 | +[edition hygiene]: #edition-hygiene  | 
 | 144 | + | 
 | 145 | +Spans are marked with the edition of the crate that the span came from.  | 
 | 146 | +See [Macro hygiene] in the Edition Guide for a user-centric description of what this means.  | 
 | 147 | + | 
 | 148 | +You should normally use the edition from the token span instead of looking at the global `Session`  | 
 | 149 | +edition.  | 
 | 150 | +For example, use `span.edition().at_least_rust_2021()` instead of `sess.at_least_rust_2021()`.  | 
 | 151 | +This helps ensure that macros behave correctly when used across crates.  | 
 | 152 | + | 
 | 153 | +[Macro hygiene]: https://doc.rust-lang.org/nightly/edition-guide/editions/advanced-migrations.html#macro-hygiene  | 
 | 154 | + | 
 | 155 | +## Lints  | 
 | 156 | + | 
 | 157 | +Lints support a few different options for interacting with editions.  | 
 | 158 | +Lints can be *future incompatible edition migration lints*, which are used to support  | 
 | 159 | +[migrations][migration lints] to newer editions.  | 
 | 160 | +Alternatively, lints can be [edition-specific](#edition-specific-lints), where they change their  | 
 | 161 | +default level starting in a specific edition.  | 
 | 162 | + | 
 | 163 | +### Migration lints  | 
 | 164 | +[migration lints]: #migration-lints  | 
 | 165 | +[migration lint]: #migration-lints  | 
 | 166 | + | 
 | 167 | +*Migration lints* are used to migrate projects from one edition to the next.  | 
 | 168 | +They are implemented with a `MachineApplicable` [suggestion](../diagnostics.md#suggestions) which  | 
 | 169 | +will rewrite code so that it will **successfully compile in both the previous and the next  | 
 | 170 | +edition**.  | 
 | 171 | +For example, the [`keyword_idents`] lint will take identifiers that conflict with a new keyword to  | 
 | 172 | +use the raw identifier syntax to avoid the conflict (for example changing `async` to `r#async`).  | 
 | 173 | + | 
 | 174 | +Migration lints must be declared with the [`FutureIncompatibilityReason::EditionError`] or  | 
 | 175 | +[`FutureIncompatibilityReason::EditionSemanticsChange`] [future-incompatible  | 
 | 176 | +option](../diagnostics.md#future-incompatible-lints) in the lint declaration:  | 
 | 177 | + | 
 | 178 | +```rust,ignore  | 
 | 179 | +declare_lint! {  | 
 | 180 | +    pub KEYWORD_IDENTS,  | 
 | 181 | +    Allow,  | 
 | 182 | +    "detects edition keywords being used as an identifier",  | 
 | 183 | +    @future_incompatible = FutureIncompatibleInfo {  | 
 | 184 | +        reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018),  | 
 | 185 | +        reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>",  | 
 | 186 | +    };  | 
 | 187 | +}  | 
 | 188 | +```  | 
 | 189 | + | 
 | 190 | +When declared like this, the lint is automatically added to the appropriate  | 
 | 191 | +`rust-20xx-compatibility` lint group.  | 
 | 192 | +When a user runs `cargo fix --edition`, cargo will pass the `--force-warn rust-20xx-compatibility`  | 
 | 193 | +flag to force all of these lints to appear during the edition migration.  | 
 | 194 | +Cargo also passes `--cap-lints=allow` so that no other lints interfere with the edition migration.  | 
 | 195 | + | 
 | 196 | +Migration lints can be either `Allow` or `Warn` by default.  | 
 | 197 | +If it is `Allow`, users usually won't see this warning unless they are doing an edition migration  | 
 | 198 | +manually or there is a problem during the migration.  | 
 | 199 | +Most migration lints are `Allow`.  | 
 | 200 | + | 
 | 201 | +If it is `Warn` by default, users on all editions will see this warning.  | 
 | 202 | +Only use `Warn` if you think it is important for everyone to be aware of the change, and to  | 
 | 203 | +encourage people to update their code on all editions.  | 
 | 204 | +Beware that new warn-by-default lint that hit many projects can be very disruptive and frustrating  | 
 | 205 | +for users.  | 
 | 206 | +You may consider switching an `Allow` to `Warn` several years after the edition stabilizes.  | 
 | 207 | +This will only show up for the relatively small number of stragglers who have not updated to the new  | 
 | 208 | +edition.  | 
 | 209 | + | 
 | 210 | +[`keyword_idents`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/allowed-by-default.html#keyword-idents  | 
 | 211 | +[`FutureIncompatibilityReason::EditionError`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.FutureIncompatibilityReason.html#variant.EditionError  | 
 | 212 | +[`FutureIncompatibilityReason::EditionSemanticsChange`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.FutureIncompatibilityReason.html#variant.EditionSemanticsChange  | 
 | 213 | + | 
 | 214 | +### Edition-specific lints  | 
 | 215 | + | 
 | 216 | +Lints can be marked so that they have a different level starting in a specific edition.  | 
 | 217 | +In the lint declaration, use the `@edition` marker:  | 
 | 218 | + | 
 | 219 | +```rust,ignore  | 
 | 220 | +declare_lint! {  | 
 | 221 | +    pub SOME_LINT_NAME,  | 
 | 222 | +    Allow,  | 
 | 223 | +    "my lint description",  | 
 | 224 | +    @edition Edition2024 => Warn;  | 
 | 225 | +}  | 
 | 226 | +```  | 
 | 227 | + | 
 | 228 | +Here, `SOME_LINT_NAME` defaults to `Allow` on all editions before 2024, and then becomes `Warn`  | 
 | 229 | +afterwards.  | 
 | 230 | + | 
 | 231 | +This should generally be used sparingly, as there are other options:  | 
 | 232 | + | 
 | 233 | +- Small impact stylistic changes unrelated to an edition can just make the lint `Warn` on all  | 
 | 234 | +  editions. If you want people to adopt a different way to write things, then go ahead and commit to  | 
 | 235 | +  having it show up for all projects.  | 
 | 236 | + | 
 | 237 | +  Beware that if a new warn-by-default lint hits many projects, it can be very disruptive and  | 
 | 238 | +  frustrating for users.  | 
 | 239 | + | 
 | 240 | +- Change the new style to be a hard error in the new edition, and use a [migration lint] to  | 
 | 241 | +  automatically convert projects to the new style. For example,  | 
 | 242 | +  [`ellipsis_inclusive_range_patterns`] is a hard error in 2021, and warns in all previous editions.  | 
 | 243 | + | 
 | 244 | +  Beware that these cannot be added after the edition stabilizes.  | 
 | 245 | + | 
 | 246 | +- Migration lints can also change over time.  | 
 | 247 | +  For example, the migration lint can start out as `Allow` by default.  | 
 | 248 | +  For people performing the migration, they will automatically get updated to the new code.  | 
 | 249 | +  Then, after some years, the lint can be made to `Warn` in previous editions.  | 
 | 250 | + | 
 | 251 | +  For example [`anonymous_parameters`] was a 2018 Edition migration lint (and a hard-error in 2018)  | 
 | 252 | +  that was `Allow` by default in previous editions.  | 
 | 253 | +  Then, three years later, it was changed to `Warn` for all previous editions, so that all users got  | 
 | 254 | +  a warning that the style was being phased out.  | 
 | 255 | +  If this was a warning from the start, it would have impacted many projects and be very disruptive.  | 
 | 256 | +  By making it part of the edition, most users eventually updated to the new edition and were  | 
 | 257 | +  handled by the migration.  | 
 | 258 | +  Switching to `Warn` only impacted a few stragglers who did not update.  | 
 | 259 | + | 
 | 260 | +[`ellipsis_inclusive_range_patterns`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#ellipsis-inclusive-range-patterns  | 
 | 261 | +[`anonymous_parameters`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#anonymous-parameters  | 
 | 262 | + | 
 | 263 | +### Lints and stability  | 
 | 264 | + | 
 | 265 | +Lints can be marked as being unstable, which can be helpful when developing a new edition feature,  | 
 | 266 | +and you want to test out a migration lint.  | 
 | 267 | +The feature gate can be specified in the lint's declaration like this:  | 
 | 268 | + | 
 | 269 | +```rust,ignore  | 
 | 270 | +declare_lint! {  | 
 | 271 | +    pub SOME_LINT_NAME,  | 
 | 272 | +    Allow,  | 
 | 273 | +    "my cool lint",  | 
 | 274 | +    @feature_gate = sym::my_feature_name;  | 
 | 275 | +}  | 
 | 276 | +```  | 
 | 277 | + | 
 | 278 | +Then, the lint will only fire if the user has the appropriate `#![feature(my_feature_name)]`.  | 
 | 279 | +Just beware that when it comes time to do crater runs testing the migration that the feature gate  | 
 | 280 | +will need to be removed.  | 
 | 281 | + | 
 | 282 | +Alternatively, you can implement an allow-by-default [migration lint] for an upcoming unstable  | 
 | 283 | +edition without a feature gate.  | 
 | 284 | +Although users may technically be able to enable the lint before the edition is stabilized, most  | 
 | 285 | +will not notice the new lint exists, and it should not disrupt anything or cause any breakage.  | 
 | 286 | + | 
 | 287 | +### Idiom lints  | 
 | 288 | + | 
 | 289 | +In the 2018 edition, there was a concept of "idiom lints" under the `rust-2018-idioms` lint group.  | 
 | 290 | +The concept was to have new idiomatic styles under a different lint group separate from the forced  | 
 | 291 | +migrations under the `rust-2018-compatibility` lint group, giving some flexibility as to how people  | 
 | 292 | +opt-in to certain edition changes.  | 
 | 293 | + | 
 | 294 | +Overall this approach did not seem to work very well,  | 
 | 295 | +and it is unlikely that we will use the idiom groups in the future.  | 
 | 296 | + | 
 | 297 | +## Standard library changes  | 
 | 298 | + | 
 | 299 | +### Preludes  | 
 | 300 | + | 
 | 301 | +Each edition comes with a specific prelude of the standard library.  | 
 | 302 | +These are implemented as regular modules in [`core::prelude`] and [`std::prelude`].  | 
 | 303 | +New items can be added to the prelude, just beware that this can conflict with user's pre-existing  | 
 | 304 | +code.  | 
 | 305 | +Usually a [migration lint] should be used to migrate existing code to avoid the conflict.  | 
 | 306 | +For example, [`rust_2021_prelude_collisions`] is used to handle the collisions with the new traits  | 
 | 307 | +in 2021.  | 
 | 308 | + | 
 | 309 | +[`core::prelude`]: https://doc.rust-lang.org/core/prelude/index.html  | 
 | 310 | +[`std::prelude`]: https://doc.rust-lang.org/std/prelude/index.html  | 
 | 311 | +[`rust_2021_prelude_collisions`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/allowed-by-default.html#rust-2021-prelude-collisions  | 
 | 312 | + | 
 | 313 | +### Customized language behavior  | 
 | 314 | + | 
 | 315 | +Usually it is not possible to make breaking changes to the standard library.  | 
 | 316 | +In some rare cases, the teams may decide that the behavior change is important enough to break this  | 
 | 317 | +rule.  | 
 | 318 | +The downside is that this requires special handling in the compiler to be able to distinguish when  | 
 | 319 | +the old and new signatures or behaviors should be used.  | 
 | 320 | + | 
 | 321 | +One example is the change in method resolution for [`into_iter()` of arrays][into-iter].  | 
 | 322 | +This was implemented with the `#[rustc_skip_array_during_method_dispatch]` attribute on the  | 
 | 323 | +`IntoIterator` trait which then tells the compiler to consider an alternate trait resolution choice  | 
 | 324 | +based on the edition.  | 
 | 325 | + | 
 | 326 | +Another example is the [`panic!` macro changes][panic-macro].  | 
 | 327 | +This required defining multiple panic macros, and having the built-in panic macro implementation  | 
 | 328 | +determine the appropriate way to expand it.  | 
 | 329 | +This also included the [`non_fmt_panics`] [migration lint] to adjust old code to the new form, which  | 
 | 330 | +required the `rustc_diagnostic_item` attribute to detect the usage of the panic macro.  | 
 | 331 | + | 
 | 332 | +In general it is recommended to avoid these special cases except for very high value situations.  | 
 | 333 | + | 
 | 334 | +[into-iter]: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html  | 
 | 335 | +[panic-macro]: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html  | 
 | 336 | +[`non_fmt_panics`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#non-fmt-panics  | 
0 commit comments