RFC 3114: Extend prelude for 2021 edition

libs ()

Summary

A new prelude for the 2021 edition, featuring several extra traits.

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, 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.

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::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::rust_2021, but not std::prelude::v1:

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

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.

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:

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

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

TryFrom/TryInto

The TryFrom/TryInto traits could not be added to the prelude without an edition boundary. This was tried 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::<Vec<_, _>>() -- 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:

(See references below.)

Prior art

Python currently has ~70 functions and types 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

Future possibilities

Future editions could add more items and/or remove some.

Previous discussions