From 74644202a84b701cafcbf83401c732116f3f8275 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 28 Jul 2020 15:52:13 -0400 Subject: [PATCH 01/16] Edition 2021 and beyond, draft 1 Co-authored-by: Steve Klabnik Small tweaks to the 2021 edition RFC add a section with core concepts of an edition --- text/0000-edition-2021-and-beyond.md | 194 +++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 text/0000-edition-2021-and-beyond.md diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md new file mode 100644 index 00000000000..286ed74a3a2 --- /dev/null +++ b/text/0000-edition-2021-and-beyond.md @@ -0,0 +1,194 @@ +- Feature Name: N/A +- Start Date: 2020-07-29 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +* Announce plans for a Rust 2021 Edition, and for a regular cadence of editions every 3 years thereafter. + * We will roll out an edition regardless of whether there are breaking changes. +* Unlike Rust 2018, we will avoid using editions as a "deadline" to tie together high-priority projects. + * Instead, we embrace the train model, but editions are effectively a "somewhat bigger release", giving us an opportunity to give an overview of all the work that has landed over the previous three years. +* We specify a cadence for Edition lints. + * "Edition idiom" lints for Edition N will warn for editions before N, and become "deny by default" in Edition N. + * Since it would be disruptive to introduce deny-by-default lints for Rust 2018 now, the Rust 2018 lints are repurposed into Rust 2021 Edition lints. +* We specify a policy on reserving keywords and other prospective changes. + * In short, reserving keywords is allowed only as part of an active project group. + +# Motivation +[motivation]: #motivation + +The plan for editions was laid out in [RFC 2052] and Rust had its first edition in 2018. This effort was in many ways a success but also resulted in some difficult lessons. As part of this year's roadmap, one of the major questions we identified was that we need to decide whether we are going to do more editions and -- if so -- how we are going to manage the process. + +[RFC 2052]: https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md + +This RFC proposes a specific answer to those questions: + +* We will do new Rust editions on a regular, three-year cadence. + * We will roll out an edition regardless of whether there are breaking changes. +* Unlike Rust 2018, we will avoid using editions as a "deadline" to tie together high-priority projects. + * Instead, we embrace the train model, but editions are effectively a "somewhat bigger release", giving us an opportunity to give an overview of all the work that has landed over the previous three years. +* We specify a cadence for Edition lints. + * "Edition idiom" lints for Edition N will warn for editions before N, and become "deny by default" in Edition N. + * Since it would be disruptive to introduce deny-by-default lints for Rust 2018 now, the Rust 2018 lints are repurposed into Rust 2021 Edition lints. +* We specify a policy on reserving keywords and other prospective changes. + * In short, reserving keywords is allowed only as part of an active project group. + +## Expected nature of editions to come + +We believe the Rust 2018 was somewhat exceptional in that it introduced changes to the module system that affected virtually every crate, even if those changes were almost completely automated. We expect that the changes introduced by most editions will be much more modest and discrete, more analogous to `async fn` (which simply introduced the `async` keyword), or the changes proposed by [RFC 2229] (which tweaks the way that closure captures work to make them more precise). + +The "size" of changes to expect is important, because they help inform the best way to ship editions. Since we expect most changes to be relatively small, we would like to design a system that allows us to judge those changes individually, without having to justify an edition by having a large number of changes combined together. Moreover, we'd like to have editions happening on a predictable cadence, so that we can take that cadence into account when designing and implementing features (i.e., so that we can try to tackle changes that may require migrations earlier, to give plenty of time). + +## Key ideas of edition do not change + +Just as with Rust 2018, we are firmly committed to the core concepts of an edition: + +* Crates using older editions continues to compile in the newer + compiler, potentially with warnings. +* Crates using different editions can interoperate, and people can + upgrade to newer editions on their own schedule. +* Code that compiles without a warning on Edition N should also + compile on Edition N + 1. +* Migration between editions should generally be automated. +* Editions make "skin-deep" changes, with all editions ultimately + compiling to a single common representation. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +We use this section to try and convey the story that average users will need to understand. + +## What is a Rust edition? + +Every three years, we introduce a new Rust Edition. These editions are named after the year in which they occur, like Rust 2015 or Rust 2018. Each crate specifies the Rust edition that it requires in its `Cargo.toml` file via a setting like `edition = "2018"`. The purpose of editions is to give us a chance to introduce "opt-in" changes like new keywords that would otherwise have the potential to break existing code. + +When we introduce a new edition, we don't remove support for the older ones, so all crates continue to compile just as they ever did. Moreover, editions are fully interoperable, so there is no possibility of an "ecosystem split". This means that you can upgrade your crates to the new edition on whatever schedule works best for you. + +The release of a new edition is always a celebratory affair. It gives us a chance to look back at all the work that has gotten done over the last three years. The "opt-in" changes also allow us to introduce new features or syntax that would otherwise be impossible. + +## How do I upgrade between editions? + +Upgrading between editions is meant to be easy. The general rule is, if your code compiles without warnings, you should be able to opt into the new edition, and your code will compile. + +Along with each edition, we also release support for it in a tool called `rustfix`, which will automatically migate your code from the old edition to the new edition, preserving semantics along the way. You may have to do a bit of cleanup after the tool runs, but it shouldn't be much. + +## "Migrations" in an edition vs "idiom lints" + +When we release a new edition, it comes together with a certain set of "migrations". Migrations are the "breaking changes" introduced by the edition, except of course that since editions are opt-in, no code actually breaks. For example, if we introduce a new keyword, you will have to rename variables or functions using the old keyword, or else use Rust's `r#keyword` feature (which allows you to use a keyword as a regular variable/function/type name). As mentioned before, the edition comes with tooling that will make these changes for you, though sometimes you will want to cleanup the resulting code afterwards. + +In addition to those migrations, editions also come with a set of "idiom lints". These lints warn against deprecated patterns that we wish to discourage, even though they continue to work. Since these are lints, they don't cause your code to stop compiling. + +To encourage folks to migrate, these lints are "stepped up" in severity based on the edition. For an idiom lint introduced as part of Edition N, the lint will warn-by-default for earlier editions, and become deny-by-default only for code in Edition N. + +As an exception, the Rust 2018 idiom lints will warn-by-default during Rust 2018 and become deny-by-default in Rust 2021 (effectively, they are being "repurposed" as 2021 idiom lints). This is because we never made them into warnings by default in 2018, and it would be disruptive to suddenly have them start erroring on existing code now. + +Like migrations, idiom lints are expected to come with automatic tooling for rewriting your code. However, in the limit, that tooling can be as simple as inserting an `#![allow(lint_x)]` at the crate level, although we'd prefer to avoid that. + +## The edition guide + +The [edition guide](https://doc.rust-lang.org/edition-guide/introduction.html) documents each of Rust's editions and the various migrations and idiom lints that were introduced as part of it. It will be updated to use the terminology from this RFC, naturally, and be updated during each edition. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +We use this section to answer detailed questions. + +## Integrating the 3-year cadence into our roadmap planning + +It is expected that the 3-year edition cadence can inform our roadmap and feature planning. In particular, larger features or features that may require migrations should be coordinated to begin work earlier in the 3-year cadence. + +## Migrations + +Migrations are the "breaking changes" that we make as part of an edition transition (except of course that they don't break any code). We want to ensure a smooth user experience, so each such change must either: + +* Have automated, rustfix-compatible tooling that will ensure that old code continues to work in the new edition with the same semantics as before. +* Or, be expected to occur *very* rarely, as evidenced by crater runs and experience. In these cases, it is preferable if the migration causes a compilation error, rather than silently changing semantics. + +In some cases, migrations can come with a combination. For example, there may be tooling to port old code that works the vast majority of the time but occasionally fails (this can happen around macros). + +## Project group to manage the edition release + +For each Edition release, we will create a project group to track the edition changes and decide on what features are to be included. This group is a subgroup of the release team, and should contain representatives from the compiler, lang, and dev-tools teams. The group is empowered to set a schedule for when changes must be ready in order to be included in the edition, and to enforce that schedule as needed -- this includes removing features from the edition if they are not ready in time or the quality is judged to be insufficient (e.g., if the migration tooling is too buggy). The edition project group is also expected to ensure that the [edition guide](https://doc.rust-lang.org/edition-guide/introduction.html) is updated with accurate information. + +## Idiom lint transitions + +"Idiom lints" are issued in a lint group named after the edition year, such as `rust_2018_idioms`. They are warn-by-default in the previous edition, and are deny by default in the new edition. + +Idiom lints are encouraged but not required to produce "rustfix"-compatible suggestions. + +## Keyword reservation policy + +One question that comes up around editions is whether to reserve keywords which we think we *might* want but for which we don't have a particular use in mind yet. For the Rust 2018 edition, we opted not to reserve any such keywords, and in this RFC we re-affirm that policy. + +The policy is that **new keywords can be introduced in an edition only as part of a design with an accepted RFC**. Note that if there is an accepted RFC for some design that introduces a new keyword, but the design is not yet fully implemented, then the edition might still make that keyword illegal. This way, the way is clear when the time comes to introduce that keyword in the future. As an example, this is what happened with async/await: the async keyword was introduced as part of the 2018 edition, but didn't do anything until later in the release cycle. + +The motivation here is that any attempt to figure out a reasonable set of keywords to reserve seems inevitably to turn into "keyword fishing", where we wind up with a long list of potential keywords. This ultimately leads to user confusion and a degraded experience. Given that editions come on a regular basis, it suffices to simply allow the keyword to be reserved in the next edition. If we really want to expose the feature earlier, then a macro or other workaround can be used in the interim (and transitioned automatically as part of the move to the next edition). + +# Drawbacks +[drawbacks]: #drawbacks + +The primary drawbacks of doing editions at all are as follows: + +* Coordinating an edition release is a stressor on the organization, as we have to coordinate the transition tooling, documentation, and other changes. This was particularly true in the original formulation of the editions, which put a heavy emphasis on the "feature-driven" nature of the 2018 Edition (i.e., the goal was to release new and exciting features, not to look back on work that had already been completed). +* Transitioning to a new edition, even if optional, is an ask for our users. Some production users expressed frustration at having to spend the time upgading their crates to the new edition. Even with tooling, the task requires time and effort to coordinate. At this stage in Rust's life, "production use" often implies "commercial use," and time and effort means "money" to them. Asking too much could harm Rust's commercial prospects, with all of the secondary impacts that has on the not-for-profit ecosystem as well. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +There are several alternative designs which we could consider. + +## Stop doing editions + +We could simply stop doing editions altogether. However, this would mean that we are no longer able to introduce new keywords or correct language features that are widely seen as missteps, and it seems like an overreaction. + +## Do editions only on demand + +An alternative would be to wait and only do an edition when we have a need for one -- i.e., when we have some particular language change in mind. But by making editions less predictable, this would complicate the discussion of new features and changes, as it introduces more variables. Under the "train model" proposed here, the timing of the edition is a known quantity that can be taken into account when designing new features. + +## Skipping editions + +Similar to the previous, we might have an edition schedule, but simply skip an edition if, in some particular year, there aren't any migrations. This remains an option, but it remains unclear whether this will ever happen, and it also adds an additional variable that complicates RFC discussions ("But if we accept this, that'll be the only reason to have an edition, and it doesn't seem worth it.") + +Furthermore, it's worth noting that many of the most important changes we introduce in Rust are not actually migrations that require an edition, but we still would like to be able to use editions to trumpet them as well as in some cases to help and phase them in. Consider a change such as the introduction of [non-lexical lifetimes][RFC 2094]. If we introduced a change like this today, we would like to be able to use it as a community rallying point in 2021. Furthermore, even though non-lexical lifetimes have ultimately been phased in for all Rust code by now, we began by supporting them only in Rust 2018 code, precisely so as to limit its effects and give us more time for testing and to ensure that all crates were working correctly. There are other upcoming changes, such as further overhauls to the borrowing system, or changes to how we resolve traits, where we may wish to make use of an edition in a similar way. + +[RFC 2094]: https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md + +## Feature-driven editions released when things are ready, but not on a fixed schedule + +An alternative to doing editions on a schedule would be to do a **feature-driven** edition. Under this model, editions would be tied to a particular set of features we want to introduce, and they would be released when those features complete. This is what Ember did with [its notion of editions](https://emberjs.com/editions/). As part of this, Ember's editions are given names ("Octane") rather than being tied to years, since it is not known when the edition will be released when planning begins. + +This model works well for larger, sweeping changes, such as the changes to module paths in Rust 2018, but it doesn't work as well for smaller, more target changes, such as those that are being considered for Rust 2021. To take one example, [RFC 2229] introduced some tweaks to how closure captures work. When that implementation is ready, it will require an edition to phase in. However, it on its own is hardly worthy of a "special edition". It may be that this change, combined with a few others, merits an edition, but that then requires that we consider "sets of changes" rather than judging each change on its own individual merits. + +[RFC 2229]: https://github.com/rust-lang/rfcs/blob/master/text/2229-capture-disjoint-fields.md + +The fact is that, in practice, we don't expect that Rust will contain a large number of "sweeping changes" like the module reform from Rust 2018. That was rather the exception and not the norm. We expect most changes to be more analogous to the introduction of `async fn`, where we simply added a keyword, or to the closure changes from [RFC 2229]. + +# Prior art +[prior-art]: #prior-art + +* [RFC 2052] introduced Rust's editions. +* Ember's notion of feature-driven editions were introduced in [Ember RFC 364](https://github.com/emberjs/rfcs/blob/master/text/0364-roadmap-2018.md). +* As noted in [RFC 2052], C/C++ and Java compilers both have ways of specifying which version of the standard the code is expected to conform to. +* The [XSLT programming language](https://www.w3.org/TR/xslt-30/) had explicit version information embedded in every program that was used to guide transitions. (Author's note: nikomatsakis used to work on an XSLT compiler and cannot resist citing this example. nikomatsakis also discovered that there is apparently an XSLT 3.0 now. 👀) + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +None. + +# Future possibilities +[future-possibilities]: #future-possibilities + +None. It's perfect. =) + +# Appendix A. Possible migrations for a Rust 2021 edition. + +At present, there are two accepted RFCs that would require migrations and which are actively being pursued. Neither represents a "large-scale" change to the compiler. + +[RFC 2229] modifies closures so that a closure like `|| ... a.b.c ...` will, in some cases, capture *just* the field `a.b.c` instead of capturing all of `a`. This can affect when values are dropped, since in some cases the older closure might have captured all of `a` and then dropped it when the closure was dropped. Most of the time this doesn't matter (and we can likely detect most of those cases). But in some cases, it might, and hence the migration would introduce a `let a = a;` statement to preserve the existing drop order. + +[RFC 2795] introduces implicit named arguments in format strings, so that one can write `panic!("error: {error_code}")` in place of `panic!("error: {error_code}", error_code=error_code)`. However, in today's code, the former is accepted and simply panics with a `&str` equal to `error: {error_code}`. A migration can detect this edge case and rewrite the panic to preserve these semantics, [as discussed on the tracking issue](https://github.com/rust-lang/rust/issues/67984#issuecomment-653909850). + +[RFC 2975]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html From f30e7fe05b6268fb07c7b5735be6ca4ff70ebe6e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 29 Jul 2020 17:09:03 -0400 Subject: [PATCH 02/16] fix RFC 2795 link --- text/0000-edition-2021-and-beyond.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 286ed74a3a2..66a377a7867 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -191,4 +191,4 @@ At present, there are two accepted RFCs that would require migrations and which [RFC 2795] introduces implicit named arguments in format strings, so that one can write `panic!("error: {error_code}")` in place of `panic!("error: {error_code}", error_code=error_code)`. However, in today's code, the former is accepted and simply panics with a `&str` equal to `error: {error_code}`. A migration can detect this edge case and rewrite the panic to preserve these semantics, [as discussed on the tracking issue](https://github.com/rust-lang/rust/issues/67984#issuecomment-653909850). -[RFC 2975]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html +[RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html From 09bc9a3b72ef976b75cb86c0e298387a74c86dec Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 29 Jul 2020 17:09:30 -0400 Subject: [PATCH 03/16] fix typos --- text/0000-edition-2021-and-beyond.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 66a377a7867..9e03b38a846 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -132,7 +132,7 @@ The motivation here is that any attempt to figure out a reasonable set of keywor The primary drawbacks of doing editions at all are as follows: * Coordinating an edition release is a stressor on the organization, as we have to coordinate the transition tooling, documentation, and other changes. This was particularly true in the original formulation of the editions, which put a heavy emphasis on the "feature-driven" nature of the 2018 Edition (i.e., the goal was to release new and exciting features, not to look back on work that had already been completed). -* Transitioning to a new edition, even if optional, is an ask for our users. Some production users expressed frustration at having to spend the time upgading their crates to the new edition. Even with tooling, the task requires time and effort to coordinate. At this stage in Rust's life, "production use" often implies "commercial use," and time and effort means "money" to them. Asking too much could harm Rust's commercial prospects, with all of the secondary impacts that has on the not-for-profit ecosystem as well. +* Transitioning to a new edition, even if optional, is an ask for our users. Some production users expressed frustration at having to spend the time upgrading their crates to the new edition. Even with tooling, the task requires time and effort to coordinate. At this stage in Rust's life, "production use" often implies "commercial use," and time and effort means "money" to them. Asking too much could harm Rust's commercial prospects, with all of the secondary impacts that has on the not-for-profit ecosystem as well. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -159,7 +159,7 @@ Furthermore, it's worth noting that many of the most important changes we introd An alternative to doing editions on a schedule would be to do a **feature-driven** edition. Under this model, editions would be tied to a particular set of features we want to introduce, and they would be released when those features complete. This is what Ember did with [its notion of editions](https://emberjs.com/editions/). As part of this, Ember's editions are given names ("Octane") rather than being tied to years, since it is not known when the edition will be released when planning begins. -This model works well for larger, sweeping changes, such as the changes to module paths in Rust 2018, but it doesn't work as well for smaller, more target changes, such as those that are being considered for Rust 2021. To take one example, [RFC 2229] introduced some tweaks to how closure captures work. When that implementation is ready, it will require an edition to phase in. However, it on its own is hardly worthy of a "special edition". It may be that this change, combined with a few others, merits an edition, but that then requires that we consider "sets of changes" rather than judging each change on its own individual merits. +This model works well for larger, sweeping changes, such as the changes to module paths in Rust 2018, but it doesn't work as well for smaller, more targeted changes, such as those that are being considered for Rust 2021. To take one example, [RFC 2229] introduced some tweaks to how closure captures work. When that implementation is ready, it will require an edition to phase in. However, it on its own is hardly worthy of a "special edition". It may be that this change, combined with a few others, merits an edition, but that then requires that we consider "sets of changes" rather than judging each change on its own individual merits. [RFC 2229]: https://github.com/rust-lang/rfcs/blob/master/text/2229-capture-disjoint-fields.md From 7ef2476cb6047b36933fff9fb51c8305c2ed19bf Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 29 Jul 2020 17:14:55 -0400 Subject: [PATCH 04/16] reword to avoid suggesting a specific list of questions --- text/0000-edition-2021-and-beyond.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 9e03b38a846..2b11c76fcb0 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -23,7 +23,7 @@ The plan for editions was laid out in [RFC 2052] and Rust had its first edition [RFC 2052]: https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md -This RFC proposes a specific answer to those questions: +This RFC proposes various clarifications to the edition process going forward: * We will do new Rust editions on a regular, three-year cadence. * We will roll out an edition regardless of whether there are breaking changes. From b1fc149fc6c602f07f10503690de304ba081f0a3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 30 Jul 2020 05:45:55 -0400 Subject: [PATCH 05/16] s/migate/migrate/ --- text/0000-edition-2021-and-beyond.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 2b11c76fcb0..709fb70de70 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -72,7 +72,7 @@ The release of a new edition is always a celebratory affair. It gives us a chanc Upgrading between editions is meant to be easy. The general rule is, if your code compiles without warnings, you should be able to opt into the new edition, and your code will compile. -Along with each edition, we also release support for it in a tool called `rustfix`, which will automatically migate your code from the old edition to the new edition, preserving semantics along the way. You may have to do a bit of cleanup after the tool runs, but it shouldn't be much. +Along with each edition, we also release support for it in a tool called `rustfix`, which will automatically migrate your code from the old edition to the new edition, preserving semantics along the way. You may have to do a bit of cleanup after the tool runs, but it shouldn't be much. ## "Migrations" in an edition vs "idiom lints" From ebf2b495ca062a334d32c0d86b0452b29f1065ae Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 21 Sep 2020 18:01:28 -0400 Subject: [PATCH 06/16] remove discussion of "celebratory affair" --- text/0000-edition-2021-and-beyond.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 709fb70de70..c468a4c2a5f 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -66,8 +66,6 @@ Every three years, we introduce a new Rust Edition. These editions are named aft When we introduce a new edition, we don't remove support for the older ones, so all crates continue to compile just as they ever did. Moreover, editions are fully interoperable, so there is no possibility of an "ecosystem split". This means that you can upgrade your crates to the new edition on whatever schedule works best for you. -The release of a new edition is always a celebratory affair. It gives us a chance to look back at all the work that has gotten done over the last three years. The "opt-in" changes also allow us to introduce new features or syntax that would otherwise be impossible. - ## How do I upgrade between editions? Upgrading between editions is meant to be easy. The general rule is, if your code compiles without warnings, you should be able to opt into the new edition, and your code will compile. From 235bd82d25f0f18088c8fb34d3f396a78e96220a Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 21 Sep 2020 18:02:32 -0400 Subject: [PATCH 07/16] clarify lint policy --- text/0000-edition-2021-and-beyond.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index c468a4c2a5f..93edf805423 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -76,9 +76,9 @@ Along with each edition, we also release support for it in a tool called `rustfi When we release a new edition, it comes together with a certain set of "migrations". Migrations are the "breaking changes" introduced by the edition, except of course that since editions are opt-in, no code actually breaks. For example, if we introduce a new keyword, you will have to rename variables or functions using the old keyword, or else use Rust's `r#keyword` feature (which allows you to use a keyword as a regular variable/function/type name). As mentioned before, the edition comes with tooling that will make these changes for you, though sometimes you will want to cleanup the resulting code afterwards. -In addition to those migrations, editions also come with a set of "idiom lints". These lints warn against deprecated patterns that we wish to discourage, even though they continue to work. Since these are lints, they don't cause your code to stop compiling. +Editions can also change the default severity of lints, so that instead of defaulting to "warn", they default to "deny" for code using the new edition. This is done to help encourage deprecation of language features or patterns that have been found to be harmful. Because lints will now default to "deny", they can feel like other migrations, but there is an important difference -- you can opt to change the lint level back to "warn" or "allow" if you don't want to change the code yet. -To encourage folks to migrate, these lints are "stepped up" in severity based on the edition. For an idiom lint introduced as part of Edition N, the lint will warn-by-default for earlier editions, and become deny-by-default only for code in Edition N. +Lints whose severity level changes with an edition are called "idiom lints". For idiom lints associated with Edition N will be warn-by-default for earlier editions, but become deny-by-default for code that opts into Edition N. (Note that the lints are typically introduced long before the edition itself, and they simply issue warnings until the Edition is released.) As an exception, the Rust 2018 idiom lints will warn-by-default during Rust 2018 and become deny-by-default in Rust 2021 (effectively, they are being "repurposed" as 2021 idiom lints). This is because we never made them into warnings by default in 2018, and it would be disruptive to suddenly have them start erroring on existing code now. From 3b57d668e5ba569df51b127e8cd381994fd3bd2f Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 21 Sep 2020 18:02:46 -0400 Subject: [PATCH 08/16] clarify limited role of edition guide --- text/0000-edition-2021-and-beyond.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 93edf805423..445d2d825aa 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -88,6 +88,8 @@ Like migrations, idiom lints are expected to come with automatic tooling for rew The [edition guide](https://doc.rust-lang.org/edition-guide/introduction.html) documents each of Rust's editions and the various migrations and idiom lints that were introduced as part of it. It will be updated to use the terminology from this RFC, naturally, and be updated during each edition. +The aim of the edition guide is to help users who are migrating code from one edition to the next. Therefore, it will discuss the migations and lints introduced as part of an edition. It will not discuss features that work across all editions, even if those features were introduced since the previous edition was released. (This marks a change from the current guide, which for example covered the `?` operator as part of Rust 2018, even though that operator can be used in Rust 2015 code.) + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 8cded7b5b108f513fd3b080b501b709627a2b75b Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 21 Sep 2020 18:02:52 -0400 Subject: [PATCH 09/16] remove discussion of project group not really a detail that needs to be specified in the RFC --- text/0000-edition-2021-and-beyond.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 445d2d825aa..77078768675 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -108,10 +108,6 @@ Migrations are the "breaking changes" that we make as part of an edition transit In some cases, migrations can come with a combination. For example, there may be tooling to port old code that works the vast majority of the time but occasionally fails (this can happen around macros). -## Project group to manage the edition release - -For each Edition release, we will create a project group to track the edition changes and decide on what features are to be included. This group is a subgroup of the release team, and should contain representatives from the compiler, lang, and dev-tools teams. The group is empowered to set a schedule for when changes must be ready in order to be included in the edition, and to enforce that schedule as needed -- this includes removing features from the edition if they are not ready in time or the quality is judged to be insufficient (e.g., if the migration tooling is too buggy). The edition project group is also expected to ensure that the [edition guide](https://doc.rust-lang.org/edition-guide/introduction.html) is updated with accurate information. - ## Idiom lint transitions "Idiom lints" are issued in a lint group named after the edition year, such as `rust_2018_idioms`. They are warn-by-default in the previous edition, and are deny by default in the new edition. From 012dd805347d7966c93ff23faaca1a1c64337fdc Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 21 Sep 2020 18:24:11 -0400 Subject: [PATCH 10/16] clarify editions and semver --- text/0000-edition-2021-and-beyond.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 77078768675..4998bd7b79e 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -90,6 +90,12 @@ The [edition guide](https://doc.rust-lang.org/edition-guide/introduction.html) d The aim of the edition guide is to help users who are migrating code from one edition to the next. Therefore, it will discuss the migations and lints introduced as part of an edition. It will not discuss features that work across all editions, even if those features were introduced since the previous edition was released. (This marks a change from the current guide, which for example covered the `?` operator as part of Rust 2018, even though that operator can be used in Rust 2015 code.) +## Editions and semver + +For semver purposes, you should think of editions as being equivalent to any other Rust feature. If Edition N is stabilized in rustc release 1.X, then upgrading your crate to edition N also means that your crate can only be compiled with rustc release 1.X. This is no different than if you added a use of some other new feature that was added in release 1.X but which is not tied to editions. + +Rust does not have an official policy on whether it is a semver breaking change to depend on new features, but most crates do establish a policy that they maintain individually. Therefore, adopting a new edition is a breaking if and only if your crate considers it a breaking change to depend on the Rust compiler release that supports that edition. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 8c12c548993676038b0d3a903150b6005a6a2dbe Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Mon, 21 Sep 2020 18:28:05 -0400 Subject: [PATCH 11/16] clarify semver more --- text/0000-edition-2021-and-beyond.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 4998bd7b79e..988918b6a86 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -92,9 +92,9 @@ The aim of the edition guide is to help users who are migrating code from one ed ## Editions and semver -For semver purposes, you should think of editions as being equivalent to any other Rust feature. If Edition N is stabilized in rustc release 1.X, then upgrading your crate to edition N also means that your crate can only be compiled with rustc release 1.X. This is no different than if you added a use of some other new feature that was added in release 1.X but which is not tied to editions. +For semver purposes, you should think of editions as being equivalent to any other Rust feature. If Edition N is stabilized in rustc release 1.X, then upgrading your crate to edition N also means that your crate can only be compiled with rustc release 1.X or later. This is no different than if you added a use of some other new feature that was added in release 1.X but which is not tied to editions. -Rust does not have an official policy on whether it is a semver breaking change to depend on new features, but most crates do establish a policy that they maintain individually. Therefore, adopting a new edition is a breaking if and only if your crate considers it a breaking change to depend on the Rust compiler release that supports that edition. +Rust does not have an official policy on whether it is a semver breaking change to change the version of the Rust compiler required to compile your crate. In practice, widely used crates generally adoptand document a "MSRV" (Minimum Supported Rust Version) and have rules about when it can be changed. Upgrading to an edition may then trigger a change to the MSRV and hence could be considered a breaking change, depending on the crate's policy. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 3cc771e6ef26a4b6c8a2c6f12e131a736748be9c Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 22 Sep 2020 17:10:22 -0400 Subject: [PATCH 12/16] fix typos --- text/0000-edition-2021-and-beyond.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 988918b6a86..603e3790247 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -88,13 +88,13 @@ Like migrations, idiom lints are expected to come with automatic tooling for rew The [edition guide](https://doc.rust-lang.org/edition-guide/introduction.html) documents each of Rust's editions and the various migrations and idiom lints that were introduced as part of it. It will be updated to use the terminology from this RFC, naturally, and be updated during each edition. -The aim of the edition guide is to help users who are migrating code from one edition to the next. Therefore, it will discuss the migations and lints introduced as part of an edition. It will not discuss features that work across all editions, even if those features were introduced since the previous edition was released. (This marks a change from the current guide, which for example covered the `?` operator as part of Rust 2018, even though that operator can be used in Rust 2015 code.) +The aim of the edition guide is to help users who are migrating code from one edition to the next. Therefore, it will discuss the migrations and lints introduced as part of an edition. It will not discuss features that work across all editions, even if those features were introduced since the previous edition was released. (This marks a change from the current guide, which for example covered the `?` operator as part of Rust 2018, even though that operator can be used in Rust 2015 code.) ## Editions and semver For semver purposes, you should think of editions as being equivalent to any other Rust feature. If Edition N is stabilized in rustc release 1.X, then upgrading your crate to edition N also means that your crate can only be compiled with rustc release 1.X or later. This is no different than if you added a use of some other new feature that was added in release 1.X but which is not tied to editions. -Rust does not have an official policy on whether it is a semver breaking change to change the version of the Rust compiler required to compile your crate. In practice, widely used crates generally adoptand document a "MSRV" (Minimum Supported Rust Version) and have rules about when it can be changed. Upgrading to an edition may then trigger a change to the MSRV and hence could be considered a breaking change, depending on the crate's policy. +Rust does not have an official policy on whether it is a semver breaking change to change the version of the Rust compiler required to compile your crate. In practice, widely used crates generally adopt and document a "MSRV" (Minimum Supported Rust Version) and have rules about when it can be changed. Upgrading to an edition may then trigger a change to the MSRV and hence could be considered a breaking change, depending on the crate's policy. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 0a4b1edba15df752de941dd5fd03d595a0e318bb Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 27 Sep 2020 10:19:46 -0400 Subject: [PATCH 13/16] pull out the note about leveraging editions for complex refactorings --- text/0000-edition-2021-and-beyond.md | 91 ++++++++++++++++------------ 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 603e3790247..fc45f94d657 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -4,36 +4,38 @@ - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary + [summary]: #summary -* Announce plans for a Rust 2021 Edition, and for a regular cadence of editions every 3 years thereafter. - * We will roll out an edition regardless of whether there are breaking changes. -* Unlike Rust 2018, we will avoid using editions as a "deadline" to tie together high-priority projects. - * Instead, we embrace the train model, but editions are effectively a "somewhat bigger release", giving us an opportunity to give an overview of all the work that has landed over the previous three years. -* We specify a cadence for Edition lints. - * "Edition idiom" lints for Edition N will warn for editions before N, and become "deny by default" in Edition N. - * Since it would be disruptive to introduce deny-by-default lints for Rust 2018 now, the Rust 2018 lints are repurposed into Rust 2021 Edition lints. -* We specify a policy on reserving keywords and other prospective changes. - * In short, reserving keywords is allowed only as part of an active project group. +- Announce plans for a Rust 2021 Edition, and for a regular cadence of editions every 3 years thereafter. + - We will roll out an edition regardless of whether there are breaking changes. +- Unlike Rust 2018, we will avoid using editions as a "deadline" to tie together high-priority projects. + - Instead, we embrace the train model, but editions are effectively a "somewhat bigger release", giving us an opportunity to give an overview of all the work that has landed over the previous three years. +- We specify a cadence for Edition lints. + - "Edition idiom" lints for Edition N will warn for editions before N, and become "deny by default" in Edition N. + - Since it would be disruptive to introduce deny-by-default lints for Rust 2018 now, the Rust 2018 lints are repurposed into Rust 2021 Edition lints. +- We specify a policy on reserving keywords and other prospective changes. + - In short, reserving keywords is allowed only as part of an active project group. # Motivation + [motivation]: #motivation The plan for editions was laid out in [RFC 2052] and Rust had its first edition in 2018. This effort was in many ways a success but also resulted in some difficult lessons. As part of this year's roadmap, one of the major questions we identified was that we need to decide whether we are going to do more editions and -- if so -- how we are going to manage the process. -[RFC 2052]: https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md +[rfc 2052]: https://github.com/rust-lang/rfcs/blob/master/text/2052-epochs.md This RFC proposes various clarifications to the edition process going forward: -* We will do new Rust editions on a regular, three-year cadence. - * We will roll out an edition regardless of whether there are breaking changes. -* Unlike Rust 2018, we will avoid using editions as a "deadline" to tie together high-priority projects. - * Instead, we embrace the train model, but editions are effectively a "somewhat bigger release", giving us an opportunity to give an overview of all the work that has landed over the previous three years. -* We specify a cadence for Edition lints. - * "Edition idiom" lints for Edition N will warn for editions before N, and become "deny by default" in Edition N. - * Since it would be disruptive to introduce deny-by-default lints for Rust 2018 now, the Rust 2018 lints are repurposed into Rust 2021 Edition lints. -* We specify a policy on reserving keywords and other prospective changes. - * In short, reserving keywords is allowed only as part of an active project group. +- We will do new Rust editions on a regular, three-year cadence. + - We will roll out an edition regardless of whether there are breaking changes. +- Unlike Rust 2018, we will avoid using editions as a "deadline" to tie together high-priority projects. + - Instead, we embrace the train model, but editions are effectively a "somewhat bigger release", giving us an opportunity to give an overview of all the work that has landed over the previous three years. +- We specify a cadence for Edition lints. + - "Edition idiom" lints for Edition N will warn for editions before N, and become "deny by default" in Edition N. + - Since it would be disruptive to introduce deny-by-default lints for Rust 2018 now, the Rust 2018 lints are repurposed into Rust 2021 Edition lints. +- We specify a policy on reserving keywords and other prospective changes. + - In short, reserving keywords is allowed only as part of an active project group. ## Expected nature of editions to come @@ -45,17 +47,18 @@ The "size" of changes to expect is important, because they help inform the best Just as with Rust 2018, we are firmly committed to the core concepts of an edition: -* Crates using older editions continues to compile in the newer +- Crates using older editions continues to compile in the newer compiler, potentially with warnings. -* Crates using different editions can interoperate, and people can +- Crates using different editions can interoperate, and people can upgrade to newer editions on their own schedule. -* Code that compiles without a warning on Edition N should also +- Code that compiles without a warning on Edition N should also compile on Edition N + 1. -* Migration between editions should generally be automated. -* Editions make "skin-deep" changes, with all editions ultimately +- Migration between editions should generally be automated. +- Editions make "skin-deep" changes, with all editions ultimately compiling to a single common representation. # Guide-level explanation + [guide-level-explanation]: #guide-level-explanation We use this section to try and convey the story that average users will need to understand. @@ -97,6 +100,7 @@ For semver purposes, you should think of editions as being equivalent to any oth Rust does not have an official policy on whether it is a semver breaking change to change the version of the Rust compiler required to compile your crate. In practice, widely used crates generally adopt and document a "MSRV" (Minimum Supported Rust Version) and have rules about when it can be changed. Upgrading to an edition may then trigger a change to the MSRV and hence could be considered a breaking change, depending on the crate's policy. # Reference-level explanation + [reference-level-explanation]: #reference-level-explanation We use this section to answer detailed questions. @@ -109,8 +113,8 @@ It is expected that the 3-year edition cadence can inform our roadmap and featur Migrations are the "breaking changes" that we make as part of an edition transition (except of course that they don't break any code). We want to ensure a smooth user experience, so each such change must either: -* Have automated, rustfix-compatible tooling that will ensure that old code continues to work in the new edition with the same semantics as before. -* Or, be expected to occur *very* rarely, as evidenced by crater runs and experience. In these cases, it is preferable if the migration causes a compilation error, rather than silently changing semantics. +- Have automated, rustfix-compatible tooling that will ensure that old code continues to work in the new edition with the same semantics as before. +- Or, be expected to occur _very_ rarely, as evidenced by crater runs and experience. In these cases, it is preferable if the migration causes a compilation error, rather than silently changing semantics. In some cases, migrations can come with a combination. For example, there may be tooling to port old code that works the vast majority of the time but occasionally fails (this can happen around macros). @@ -122,21 +126,29 @@ Idiom lints are encouraged but not required to produce "rustfix"-compatible sugg ## Keyword reservation policy -One question that comes up around editions is whether to reserve keywords which we think we *might* want but for which we don't have a particular use in mind yet. For the Rust 2018 edition, we opted not to reserve any such keywords, and in this RFC we re-affirm that policy. +One question that comes up around editions is whether to reserve keywords which we think we _might_ want but for which we don't have a particular use in mind yet. For the Rust 2018 edition, we opted not to reserve any such keywords, and in this RFC we re-affirm that policy. The policy is that **new keywords can be introduced in an edition only as part of a design with an accepted RFC**. Note that if there is an accepted RFC for some design that introduces a new keyword, but the design is not yet fully implemented, then the edition might still make that keyword illegal. This way, the way is clear when the time comes to introduce that keyword in the future. As an example, this is what happened with async/await: the async keyword was introduced as part of the 2018 edition, but didn't do anything until later in the release cycle. The motivation here is that any attempt to figure out a reasonable set of keywords to reserve seems inevitably to turn into "keyword fishing", where we wind up with a long list of potential keywords. This ultimately leads to user confusion and a degraded experience. Given that editions come on a regular basis, it suffices to simply allow the keyword to be reserved in the next edition. If we really want to expose the feature earlier, then a macro or other workaround can be used in the interim (and transitioned automatically as part of the move to the next edition). +## Leveraging editions for phasing in large changes + +In some cases, we may leverage editions for phasing in changes which will ultimately be used for all versions of Rust. As an example, consider the introduction of [non-lexical lifetimes][rfc 2094]. Implementing that RFC required introducing an entirely new version of the borrow checker. This new borrow checker included a number of bugfixes that, while valid, had the effect of causing existing code not to compile. Therefore, we didn't want to phase it in all at once. Moreover, since this was new code, we wanted to give it some time to be used in practice to help uncover problems. We solved these issues by first deploying the new borrow checker only for Rust 2018 code. This limited its effects and gave us more time for testing. Once we were more confident in the new code, we were able to start issuing warnings for Rust 2015 code and eventually removing the old borrow checker altogether. There are other upcoming changes, such as further overhauls to the borrowing system, or changes to how we resolve traits, where we may wish to make use of an edition in a similar way. + +[rfc 2094]: https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md + # Drawbacks + [drawbacks]: #drawbacks The primary drawbacks of doing editions at all are as follows: -* Coordinating an edition release is a stressor on the organization, as we have to coordinate the transition tooling, documentation, and other changes. This was particularly true in the original formulation of the editions, which put a heavy emphasis on the "feature-driven" nature of the 2018 Edition (i.e., the goal was to release new and exciting features, not to look back on work that had already been completed). -* Transitioning to a new edition, even if optional, is an ask for our users. Some production users expressed frustration at having to spend the time upgrading their crates to the new edition. Even with tooling, the task requires time and effort to coordinate. At this stage in Rust's life, "production use" often implies "commercial use," and time and effort means "money" to them. Asking too much could harm Rust's commercial prospects, with all of the secondary impacts that has on the not-for-profit ecosystem as well. +- Coordinating an edition release is a stressor on the organization, as we have to coordinate the transition tooling, documentation, and other changes. This was particularly true in the original formulation of the editions, which put a heavy emphasis on the "feature-driven" nature of the 2018 Edition (i.e., the goal was to release new and exciting features, not to look back on work that had already been completed). +- Transitioning to a new edition, even if optional, is an ask for our users. Some production users expressed frustration at having to spend the time upgrading their crates to the new edition. Even with tooling, the task requires time and effort to coordinate. At this stage in Rust's life, "production use" often implies "commercial use," and time and effort means "money" to them. Asking too much could harm Rust's commercial prospects, with all of the secondary impacts that has on the not-for-profit ecosystem as well. # Rationale and alternatives + [rationale-and-alternatives]: #rationale-and-alternatives There are several alternative designs which we could consider. @@ -153,34 +165,33 @@ An alternative would be to wait and only do an edition when we have a need for o Similar to the previous, we might have an edition schedule, but simply skip an edition if, in some particular year, there aren't any migrations. This remains an option, but it remains unclear whether this will ever happen, and it also adds an additional variable that complicates RFC discussions ("But if we accept this, that'll be the only reason to have an edition, and it doesn't seem worth it.") -Furthermore, it's worth noting that many of the most important changes we introduce in Rust are not actually migrations that require an edition, but we still would like to be able to use editions to trumpet them as well as in some cases to help and phase them in. Consider a change such as the introduction of [non-lexical lifetimes][RFC 2094]. If we introduced a change like this today, we would like to be able to use it as a community rallying point in 2021. Furthermore, even though non-lexical lifetimes have ultimately been phased in for all Rust code by now, we began by supporting them only in Rust 2018 code, precisely so as to limit its effects and give us more time for testing and to ensure that all crates were working correctly. There are other upcoming changes, such as further overhauls to the borrowing system, or changes to how we resolve traits, where we may wish to make use of an edition in a similar way. - -[RFC 2094]: https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md - ## Feature-driven editions released when things are ready, but not on a fixed schedule An alternative to doing editions on a schedule would be to do a **feature-driven** edition. Under this model, editions would be tied to a particular set of features we want to introduce, and they would be released when those features complete. This is what Ember did with [its notion of editions](https://emberjs.com/editions/). As part of this, Ember's editions are given names ("Octane") rather than being tied to years, since it is not known when the edition will be released when planning begins. This model works well for larger, sweeping changes, such as the changes to module paths in Rust 2018, but it doesn't work as well for smaller, more targeted changes, such as those that are being considered for Rust 2021. To take one example, [RFC 2229] introduced some tweaks to how closure captures work. When that implementation is ready, it will require an edition to phase in. However, it on its own is hardly worthy of a "special edition". It may be that this change, combined with a few others, merits an edition, but that then requires that we consider "sets of changes" rather than judging each change on its own individual merits. -[RFC 2229]: https://github.com/rust-lang/rfcs/blob/master/text/2229-capture-disjoint-fields.md +[rfc 2229]: https://github.com/rust-lang/rfcs/blob/master/text/2229-capture-disjoint-fields.md The fact is that, in practice, we don't expect that Rust will contain a large number of "sweeping changes" like the module reform from Rust 2018. That was rather the exception and not the norm. We expect most changes to be more analogous to the introduction of `async fn`, where we simply added a keyword, or to the closure changes from [RFC 2229]. # Prior art + [prior-art]: #prior-art -* [RFC 2052] introduced Rust's editions. -* Ember's notion of feature-driven editions were introduced in [Ember RFC 364](https://github.com/emberjs/rfcs/blob/master/text/0364-roadmap-2018.md). -* As noted in [RFC 2052], C/C++ and Java compilers both have ways of specifying which version of the standard the code is expected to conform to. -* The [XSLT programming language](https://www.w3.org/TR/xslt-30/) had explicit version information embedded in every program that was used to guide transitions. (Author's note: nikomatsakis used to work on an XSLT compiler and cannot resist citing this example. nikomatsakis also discovered that there is apparently an XSLT 3.0 now. 👀) +- [RFC 2052] introduced Rust's editions. +- Ember's notion of feature-driven editions were introduced in [Ember RFC 364](https://github.com/emberjs/rfcs/blob/master/text/0364-roadmap-2018.md). +- As noted in [RFC 2052], C/C++ and Java compilers both have ways of specifying which version of the standard the code is expected to conform to. +- The [XSLT programming language](https://www.w3.org/TR/xslt-30/) had explicit version information embedded in every program that was used to guide transitions. (Author's note: nikomatsakis used to work on an XSLT compiler and cannot resist citing this example. nikomatsakis also discovered that there is apparently an XSLT 3.0 now. 👀) # Unresolved questions + [unresolved-questions]: #unresolved-questions None. # Future possibilities + [future-possibilities]: #future-possibilities None. It's perfect. =) @@ -189,8 +200,8 @@ None. It's perfect. =) At present, there are two accepted RFCs that would require migrations and which are actively being pursued. Neither represents a "large-scale" change to the compiler. -[RFC 2229] modifies closures so that a closure like `|| ... a.b.c ...` will, in some cases, capture *just* the field `a.b.c` instead of capturing all of `a`. This can affect when values are dropped, since in some cases the older closure might have captured all of `a` and then dropped it when the closure was dropped. Most of the time this doesn't matter (and we can likely detect most of those cases). But in some cases, it might, and hence the migration would introduce a `let a = a;` statement to preserve the existing drop order. +[RFC 2229] modifies closures so that a closure like `|| ... a.b.c ...` will, in some cases, capture _just_ the field `a.b.c` instead of capturing all of `a`. This can affect when values are dropped, since in some cases the older closure might have captured all of `a` and then dropped it when the closure was dropped. Most of the time this doesn't matter (and we can likely detect most of those cases). But in some cases, it might, and hence the migration would introduce a `let a = a;` statement to preserve the existing drop order. [RFC 2795] introduces implicit named arguments in format strings, so that one can write `panic!("error: {error_code}")` in place of `panic!("error: {error_code}", error_code=error_code)`. However, in today's code, the former is accepted and simply panics with a `&str` equal to `error: {error_code}`. A migration can detect this edge case and rewrite the panic to preserve these semantics, [as discussed on the tracking issue](https://github.com/rust-lang/rust/issues/67984#issuecomment-653909850). -[RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html +[rfc 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html From abe46e764a2f81e15d4c50602b1501ea766454c1 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 27 Sep 2020 10:23:57 -0400 Subject: [PATCH 14/16] clarify that an "empty edition" is an unresolved question --- text/0000-edition-2021-and-beyond.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index fc45f94d657..9673f156553 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -161,10 +161,6 @@ We could simply stop doing editions altogether. However, this would mean that we An alternative would be to wait and only do an edition when we have a need for one -- i.e., when we have some particular language change in mind. But by making editions less predictable, this would complicate the discussion of new features and changes, as it introduces more variables. Under the "train model" proposed here, the timing of the edition is a known quantity that can be taken into account when designing new features. -## Skipping editions - -Similar to the previous, we might have an edition schedule, but simply skip an edition if, in some particular year, there aren't any migrations. This remains an option, but it remains unclear whether this will ever happen, and it also adds an additional variable that complicates RFC discussions ("But if we accept this, that'll be the only reason to have an edition, and it doesn't seem worth it.") - ## Feature-driven editions released when things are ready, but not on a fixed schedule An alternative to doing editions on a schedule would be to do a **feature-driven** edition. Under this model, editions would be tied to a particular set of features we want to introduce, and they would be released when those features complete. This is what Ember did with [its notion of editions](https://emberjs.com/editions/). As part of this, Ember's editions are given names ("Octane") rather than being tied to years, since it is not known when the edition will be released when planning begins. @@ -188,7 +184,9 @@ The fact is that, in practice, we don't expect that Rust will contain a large nu [unresolved-questions]: #unresolved-questions -None. +## Skipping editions + +In the future, it may happen that we have an edition year but we have no migrations or idiom lints planned for use in that edition. In that case, we will have to decide whether to issue a new edition anyway (to preserve consistency) or whether to skip the edition. The advantage of issuing the edition anyway is that it ensures that "every 3 years, there is an edition", making editions predictable. However, it is not clear what purpose (beyond consistency) releasing a new edition which is completely equivalent to the old edition would serve. Since this scenario may never arise in practice, we choose to defer the question. # Future possibilities From 329cbe01feff236033e7a5752e2614acc2f4dce8 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 27 Sep 2020 10:45:57 -0400 Subject: [PATCH 15/16] clarify migration/idiom-lint policy --- text/0000-edition-2021-and-beyond.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 9673f156553..2269776683d 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -79,9 +79,11 @@ Along with each edition, we also release support for it in a tool called `rustfi When we release a new edition, it comes together with a certain set of "migrations". Migrations are the "breaking changes" introduced by the edition, except of course that since editions are opt-in, no code actually breaks. For example, if we introduce a new keyword, you will have to rename variables or functions using the old keyword, or else use Rust's `r#keyword` feature (which allows you to use a keyword as a regular variable/function/type name). As mentioned before, the edition comes with tooling that will make these changes for you, though sometimes you will want to cleanup the resulting code afterwards. -Editions can also change the default severity of lints, so that instead of defaulting to "warn", they default to "deny" for code using the new edition. This is done to help encourage deprecation of language features or patterns that have been found to be harmful. Because lints will now default to "deny", they can feel like other migrations, but there is an important difference -- you can opt to change the lint level back to "warn" or "allow" if you don't want to change the code yet. +Migrations target the "intersection" of editions. That is, a migration will convert code in Edition N into code that compiles in **both** Edition N and Edition N+1. This permits migrations to be applied in a piecemeal fashion rather than forcing all migrations to occur at once. -Lints whose severity level changes with an edition are called "idiom lints". For idiom lints associated with Edition N will be warn-by-default for earlier editions, but become deny-by-default for code that opts into Edition N. (Note that the lints are typically introduced long before the edition itself, and they simply issue warnings until the Edition is released.) +Because migrations produce code that compiles in both Edition N and Edition N+1, that code may not be idiomatic in the new edition. To address this, each edition can also include **idiom lints**. These are lints which identify deprecated or altered patterns from older editions and convert them into the newer style. Some of these lints will produce code that that is only valid in edition N+1, in which case the lints will only be triggered in the new edition. + +In other cases, idiom lints may be simply migrating away from a deprecated pattern. In that case, those lints will default to "warn" for older editions (edition N and before) but change their severity to "deny" for code using the new edition (edition N+1 and beyond). Because lints will now default to "deny", they can feel like other migrations, but there is an important difference -- you can opt to change the lint level back to "warn" or "allow" if you don't want to change the code yet. (Note that such lints are typically introduced long before the edition itself, and they simply issue warnings until the Edition is released.) As an exception, the Rust 2018 idiom lints will warn-by-default during Rust 2018 and become deny-by-default in Rust 2021 (effectively, they are being "repurposed" as 2021 idiom lints). This is because we never made them into warnings by default in 2018, and it would be disruptive to suddenly have them start erroring on existing code now. From 178e6466857c46a290eede55d9d194ea0e62c3f1 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 30 Sep 2020 10:05:38 -0400 Subject: [PATCH 16/16] Update text/0000-edition-2021-and-beyond.md Co-authored-by: Philipp Krones --- text/0000-edition-2021-and-beyond.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-edition-2021-and-beyond.md b/text/0000-edition-2021-and-beyond.md index 2269776683d..cb328cb64d5 100644 --- a/text/0000-edition-2021-and-beyond.md +++ b/text/0000-edition-2021-and-beyond.md @@ -81,7 +81,7 @@ When we release a new edition, it comes together with a certain set of "migratio Migrations target the "intersection" of editions. That is, a migration will convert code in Edition N into code that compiles in **both** Edition N and Edition N+1. This permits migrations to be applied in a piecemeal fashion rather than forcing all migrations to occur at once. -Because migrations produce code that compiles in both Edition N and Edition N+1, that code may not be idiomatic in the new edition. To address this, each edition can also include **idiom lints**. These are lints which identify deprecated or altered patterns from older editions and convert them into the newer style. Some of these lints will produce code that that is only valid in edition N+1, in which case the lints will only be triggered in the new edition. +Because migrations produce code that compiles in both Edition N and Edition N+1, that code may not be idiomatic in the new edition. To address this, each edition can also include **idiom lints**. These are lints which identify deprecated or altered patterns from older editions and convert them into the newer style. Some of these lints will produce code that is only valid in edition N+1, in which case the lints will only be triggered in the new edition. In other cases, idiom lints may be simply migrating away from a deprecated pattern. In that case, those lints will default to "warn" for older editions (edition N and before) but change their severity to "deny" for code using the new edition (edition N+1 and beyond). Because lints will now default to "deny", they can feel like other migrations, but there is an important difference -- you can opt to change the lint level back to "warn" or "allow" if you don't want to change the code yet. (Note that such lints are typically introduced long before the edition itself, and they simply issue warnings until the Edition is released.)