From b88bcf0555971dfd267c6f6eace3f5d026df36ce Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 26 Apr 2021 22:41:16 +0200 Subject: [PATCH 1/4] Initial version of prelude-2021 RFC (traits only) --- text/0000-prelude-2021-traits.md | 125 +++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 text/0000-prelude-2021-traits.md diff --git a/text/0000-prelude-2021-traits.md b/text/0000-prelude-2021-traits.md new file mode 100644 index 00000000000..4c51b3d8e15 --- /dev/null +++ b/text/0000-prelude-2021-traits.md @@ -0,0 +1,125 @@ +- Feature Name: `prelude_2021_traits` +- Start Date: 2021-02-16 +- 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 + +A new prelude for the 2021 edition, featuring several extra traits. + +# Motivation +[motivation]: #motivation + +While types and free functions can be added to the prelude independent of edition boundaries, the same is not true for traits. Adding a trait to the prelude can cause compatibility issues because calls to methods named the same as methods of the newly in scope traits can become ambiguous. While such changes are technically +[minor][minor-change], such that some backwards compatibility breaks are potentially allowed, the possibility of relatively widespread breakage makes it less attractive to do so without an edition opt-in. + +The expected outcome is to make the newly added traits more accessible to users, potentially stimulating their use compared to alternatives. + +[minor-change]: https://rust-lang.github.io/rfcs/1105-api-evolution.html#minor-change-adding-a-defaulted-item + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The compiler currently brings all items from `std::prelude::v1` into scope for each module (for code using `std`). Crates that declare usage of the 2021 edition will instead get items from `std::prelude::edition2021` imported into scope. With the implementation of this RFC, we will add `edition2015` and `edition2018` modules in `std::prelude`, and a crate configuring a particular edition will get the prelude appropriate for its edition. The `v1` module will stick around for compatibility reasons, although it might be deprecated in the future. + +The `core` prelude will parallel the `std` prelude, containing the same structure and the same items as far as they are available in `core`. + +This RFC proposes to add the following traits to `std::prelude::edition2021`, but not `std::prelude::v1`: + +- `TryFrom`/`TryInto` +- `FromIterator` + +Except for newly added traits, the `v1` and `edition2021` preludes should be kept in sync. Items that are added to the `edition2021` prelude should also be added to the `v1` prelude if there is negligible risk for compatibility. On the other hand, newer edition preludes may decide not to include items from an older edition's prelude. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The new prelude will be named after the edition that introduces it to make the link to the edition more obvious. Each edition will have a separate prelude, whether the contents are actually different or not. + +Importantly, we'll need to make the upgrade path to an edition containing new traits in the prelude as smooth as possible. The analysis can be done as follows: + +* Find method calls matching the name of one of the newly added traits' methods. + This can be done either by hardcoding these method names or by setting up some + kind of registry through the use of an attribute on the relevant traits. +* After method resolution, if the method matches one of the newly added methods' + names but the originating trait is not from `core` or `std` (and/or does not + match the originating trait), suggest a rewrite to disambiguating syntax + (such as `foo.try_into()` to `TryInto::try_into(foo)`). + +Currently, diagnostics for trait methods that are not in scope suggest importing the originating trait. For traits that have become part of the prelude in a newer edition, the diagnostics should be updated such that they suggest upgrading to the latest edition as an alternative to importing the relevant trait. + +# Drawbacks +[drawbacks]: #drawbacks + +Making the prelude contents edition-dependent makes the difference between different editions larger, and could thus increase confusion especially in the early phase when older editions and newer ones are used in parallel. + +Adding more traits to the prelude makes methods from other traits using the same names as prelude traits harder to access, requiring calls to explicitly disambiguate (`TryFrom::try_from(foo)`). + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## `TryFrom`/`TryInto` + +The `TryFrom`/`TryInto` traits could not be added to the prelude without an edition boundary. This was [tried](https://github.com/rust-lang/rust/pull/49305) around the time of the 2018 edition, but failed due to breaking substantial parts of the ecosystem. However, `TryFrom`/`TryInto` offer a generic conversion mechanism that is more robust than `as` because the conversions are explicitly fallible. Their usage is more widely applicable than the `From`/`Into` traits because they account for fallibility of the conversions. + +Without doing this, the `TryFrom`/`TryInto` traits remain less accessible than the infallible `From`/`Into` conversion traits, providing a disincentive to implement/use fallible conversions even where conversion operations are intrinsically fallible. + +## `FromIterator` + +The documentation for `FromIterator`'s `from_iter()` method currently reads: + +> `FromIterator::from_iter()` is rarely called explicitly, and is instead used through `Iterator::collect()` method. See `Iterator::collect()`'s documentation for more examples. + +However, it is reasonably common that type inferencing fails to infer the full type of the target type, in which case an explicit type annotation or turbofishing is needed (such as `iter.collect::>()` -- the type of the iterator item is available, so wildcards can be used for this). In these cases, `Vec::from_iter(iter)` can be a more concise and readable way to spell this, which would be easier if the trait was in scope. + +## Other traits that have been suggested for inclusion + +Other traits that have been suggested as prelude candidates: + +- `std::ops::Not`: for chaining purposes, it is sometimes useful to have an trailing `.not()` call rather than the prefix `!` operator. Therefore, [it has been suggested](https://internals.rust-lang.org/t/pre-rfc-adding-bool-not-method/13935) that the `Not` trait which brings that method in scope could be added to the prelude. This RFC author feels this use case is better served by adding an inherent impl to `bool`, since that serves the majority of the same use case with less complexity. +- `std::fmt::Display`: users of this trait generally don't need to import it because the `ToString` trait which relies on the `Display` implementation is already in the prelude. Implementers of the `Display` trait however need several other items from `std::fmt` to do so; therefore, just importing `Display` into the prelude does not help much. This RFC suggests adding `fmt` to the prelude to improve this use case. +- `std::fmt::Debug`: similar to `Display`, although there's no `to_debug()`. However, usage will usually go through `dbg!()` or the formatting mechanism (as a `{:?}` format string). +- `std::future::Future`: `Future`'s `poll()` method is usually not called directly, but most often used via `async`/`await`, therefore including `Future` in the prelude does not seem as useful. +- `std::error::Error`: while this is commonly implemented, calling its methods directly does not seem to be common enough that adding this is as useful. + +(See [references](#references) below.) + +# Prior art +[prior-art]: #prior-art + +Python currently has [~70 functions and types](https://docs.python.org/3/library/functions.html) in `__builtins__` (not counting exception types and some interpreter internals). + +`std::prelude::v1` currently contains 5 types, 26 traits, 4 variants, 22 macros and 1 free function (not including primitive types like bool, char, integers, floating points and slices). + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +* Should there be compatibility lints warning about methods named the same as proposed prelude edition trait methods? +* How do per-prelude editions affect macro hygiene/resolution? + +# Future possibilities +[future-possibilities]: #future-possibilities + +Future editions could add more items and/or remove some. + +# Previous discussions +[references]: #references + +* [Rust Edition 2021](https://hackmd.io/3eG6OZWHRbSMxoRxzwNhGQ?view#-Prelude-changes) +* [Zulip: Prelude 2021](https://rust-lang.zulipchat.com/#narrow/stream/268952-edition/topic/Prelude.202021) +* [Zulip: T-lang asking T-libs for per-edition preludes coordination](https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/T-lang.20asking.20T-libs.20for.20per-edition.20preludes.20coordination) +* [GitHub: MCP: per-edition preludes](https://github.com/rust-lang/lang-team/issues/44) +* [GitHub: Tracking issue for vNext edition prelude](https://github.com/rust-lang/rust/issues/65512) +* [GitHub: Tracking issue for the 2018 edition’s prelude](https://github.com/rust-lang/rust/issues/51418) +* [IRLO: Add `std::mem::{swap, replace, take}` to the prelude](https://internals.rust-lang.org/t/add-std-swap-replace-take-to-the-prelude/14035) +* [IRLO: Propose additions to std::prelude](https://internals.rust-lang.org/t/propose-additions-to-std-prelude/7189) +* [IRLO: `std::prelude::v2` with `TryFrom`/`TryInto` on edition 2021?](https://internals.rust-lang.org/t/std-v2-with-tryfrom-tryinto-on-edition-2021/12157) +* [IRLO: Should the Future trait be part of the prelude](https://internals.rust-lang.org/t/should-the-future-trait-be-part-of-the-prelude/10669) +* [IRLO: Random Idea: A new prelude for Rust 2018?](https://internals.rust-lang.org/t/random-idea-a-new-prelude-for-rust-2018/7158) +* [IRLO: [pre-RFC] Adding `bool::not()` method](https://internals.rust-lang.org/t/pre-rfc-adding-bool-not-method/13935) +* [IRLO: Pre-pre-RFC: syntactic sugar for `Default::default()`](https://internals.rust-lang.org/t/pre-pre-rfc-syntactic-sugar-for-default-default/13234/10) +* [IRLO: Am I the only one confused by `a.min(b)` and `a.max(b)`?](https://internals.rust-lang.org/t/am-i-the-only-one-confused-by-a-min-b-and-a-max-b/13252) +* [IRLO: The is_not_empty() method as more clearly alternative for !is_empty()](https://internals.rust-lang.org/t/the-is-not-empty-method-as-more-clearly-alternative-for-is-empty/10612) +* [IRLO: Pre-RFC: Add FromIterator to the prelude](https://internals.rust-lang.org/t/pre-rfc-add-fromiterator-to-the-prelude/4324) +* [IRLO: I analysed 5000 crates to find the most common standard library imports](https://internals.rust-lang.org/t/i-analysed-5000-crates-to-find-the-most-common-standard-library-imports/12218) From ca92f7003df07561b4a2bc1e451820bc3efe4d7d Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 30 Apr 2021 22:12:49 +0200 Subject: [PATCH 2/4] Update text/0000-prelude-2021-traits.md Co-authored-by: Kornel --- text/0000-prelude-2021-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-prelude-2021-traits.md b/text/0000-prelude-2021-traits.md index 4c51b3d8e15..0589c6eba5d 100644 --- a/text/0000-prelude-2021-traits.md +++ b/text/0000-prelude-2021-traits.md @@ -78,7 +78,7 @@ However, it is reasonably common that type inferencing fails to infer the full t Other traits that have been suggested as prelude candidates: - `std::ops::Not`: for chaining purposes, it is sometimes useful to have an trailing `.not()` call rather than the prefix `!` operator. Therefore, [it has been suggested](https://internals.rust-lang.org/t/pre-rfc-adding-bool-not-method/13935) that the `Not` trait which brings that method in scope could be added to the prelude. This RFC author feels this use case is better served by adding an inherent impl to `bool`, since that serves the majority of the same use case with less complexity. -- `std::fmt::Display`: users of this trait generally don't need to import it because the `ToString` trait which relies on the `Display` implementation is already in the prelude. Implementers of the `Display` trait however need several other items from `std::fmt` to do so; therefore, just importing `Display` into the prelude does not help much. This RFC suggests adding `fmt` to the prelude to improve this use case. +- `std::fmt::Display`: users of this trait generally don't need to import it because the `ToString` trait which relies on the `Display` implementation is already in the prelude. Implementers of the `Display` trait however need several other items from `std::fmt` to do so; therefore, just importing `Display` into the prelude does not help much. - `std::fmt::Debug`: similar to `Display`, although there's no `to_debug()`. However, usage will usually go through `dbg!()` or the formatting mechanism (as a `{:?}` format string). - `std::future::Future`: `Future`'s `poll()` method is usually not called directly, but most often used via `async`/`await`, therefore including `Future` in the prelude does not seem as useful. - `std::error::Error`: while this is commonly implemented, calling its methods directly does not seem to be common enough that adding this is as useful. From b6c02d0e521a58c05c1da663b60d40f6da5241c4 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 30 Apr 2021 22:17:43 +0200 Subject: [PATCH 3/4] Update text/0000-prelude-2021-traits.md Co-authored-by: Mara Bos --- text/0000-prelude-2021-traits.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/0000-prelude-2021-traits.md b/text/0000-prelude-2021-traits.md index 0589c6eba5d..3677b685f6b 100644 --- a/text/0000-prelude-2021-traits.md +++ b/text/0000-prelude-2021-traits.md @@ -37,7 +37,11 @@ Except for newly added traits, the `v1` and `edition2021` preludes should be kep The new prelude will be named after the edition that introduces it to make the link to the edition more obvious. Each edition will have a separate prelude, whether the contents are actually different or not. -Importantly, we'll need to make the upgrade path to an edition containing new traits in the prelude as smooth as possible. The analysis can be done as follows: +## Migration Lint + +As for all edition changes, we will implement a migration lint to detect cases where code would break in the new edition. It includes a MachineApplicable suggestion for an alternative that will work in both the current and next edition. + +The migration lint will be implemented as follows: * Find method calls matching the name of one of the newly added traits' methods. This can be done either by hardcoding these method names or by setting up some From 8f1618ad72489897810b0097a8ec0ea5d4c2684f Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Fri, 30 Apr 2021 22:19:19 +0200 Subject: [PATCH 4/4] Address review feedback --- text/0000-prelude-2021-traits.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/text/0000-prelude-2021-traits.md b/text/0000-prelude-2021-traits.md index 3677b685f6b..bc45d563012 100644 --- a/text/0000-prelude-2021-traits.md +++ b/text/0000-prelude-2021-traits.md @@ -21,16 +21,16 @@ The expected outcome is to make the newly added traits more accessible to users, # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -The compiler currently brings all items from `std::prelude::v1` into scope for each module (for code using `std`). Crates that declare usage of the 2021 edition will instead get items from `std::prelude::edition2021` imported into scope. With the implementation of this RFC, we will add `edition2015` and `edition2018` modules in `std::prelude`, and a crate configuring a particular edition will get the prelude appropriate for its edition. The `v1` module will stick around for compatibility reasons, although it might be deprecated in the future. +The compiler currently brings all items from `std::prelude::v1` into scope for each module (for code using `std`). Crates that declare usage of the 2021 edition will instead get items from `std::prelude::rust_2021` imported into scope. With the implementation of this RFC, we will add `rust_2015` and `rust_2018` modules in `std::prelude`, and a crate configuring a particular edition will get the prelude appropriate for its edition. The `v1` module will stick around for compatibility reasons, although it might be deprecated in the future. The `core` prelude will parallel the `std` prelude, containing the same structure and the same items as far as they are available in `core`. -This RFC proposes to add the following traits to `std::prelude::edition2021`, but not `std::prelude::v1`: +This RFC proposes to add the following traits to `std::prelude::rust_2021`, but not `std::prelude::v1`: - `TryFrom`/`TryInto` - `FromIterator` -Except for newly added traits, the `v1` and `edition2021` preludes should be kept in sync. Items that are added to the `edition2021` prelude should also be added to the `v1` prelude if there is negligible risk for compatibility. On the other hand, newer edition preludes may decide not to include items from an older edition's prelude. +Except for newly added traits, the `v1` and `rust_2021` preludes should be kept in sync. Items that are added to the `rust_2021` prelude should also be added to the `v1` prelude if there is negligible risk for compatibility. On the other hand, newer edition preludes may decide not to include items from an older edition's prelude. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -46,10 +46,13 @@ The migration lint will be implemented as follows: * Find method calls matching the name of one of the newly added traits' methods. This can be done either by hardcoding these method names or by setting up some kind of registry through the use of an attribute on the relevant traits. -* After method resolution, if the method matches one of the newly added methods' - names but the originating trait is not from `core` or `std` (and/or does not - match the originating trait), suggest a rewrite to disambiguating syntax - (such as `foo.try_into()` to `TryInto::try_into(foo)`). +* After method resolution, if: + * The method matches one of the newly added methods' names, and + * The originating trait is not from `core` or `std` (and/or does not + match the originating trait), and + * The originating trait is also implemented for the receiver's type, + * Suggest a rewrite to disambiguating syntax (such as `foo.try_into()` to `TryInto::try_into(foo)`). If necessary, additional levels of (de)referencing + might be needed to match the implementing type of the target trait. Currently, diagnostics for trait methods that are not in scope suggest importing the originating trait. For traits that have become part of the prelude in a newer edition, the diagnostics should be updated such that they suggest upgrading to the latest edition as an alternative to importing the relevant trait.