Skip to content

Latest commit

 

History

History
172 lines (112 loc) · 13.9 KB

0000-prelude-2021.md

File metadata and controls

172 lines (112 loc) · 13.9 KB

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. Because editions are opt-in, they represent an opportunity to add new traits to the prelude.

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::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

Apart from the 2021-specific prelude, this RFC also proposes to add several items to preludes for all editions. These changes do not have to be aligned with an edition boundary, since there is no compatibility hazard (explicitly imported names always shadow prelude names).

  • std::{mem, fmt}
  • std::collections::{HashMap, HashSet}
  • std::path::{Path, PathBuf}
  • std::sync::Arc
  • std::borrow::Cow
  • std::fs::File
  • std::cmp::{min, max}

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

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. To that end, cargo fix will update code that calls try_from() or try_into() in a scope that does not have the std traits in scope to use the explicit disambiguation syntax.

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 in addition 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 (<usize as try_from::TryFrom>::try_from()).

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::<HashMap<_, _>>() -- the type of the iterator item is available, so wildcards can be used for this). In these cases, HashMap::from_iter(iter) can be a more concise and readable way to spell this, which would be easier if the trait was in scope.

std::{mem, fmt}

It is reasonably common to use functions from the mem module such as replace() or swap(). These method names are generic enough that it is useful to keep the mem:: prefix explicitly. Providing a mem name as part of the prelude makes this pattern more obvious and, given the short module name, will not require any imports to make use of the items in this module.

Explicit (non-derive) implementations of Debug and Display are relatively common and require a set of items from the fmt module (the trait itself, the Formatter type and the fmt::Result type).

std::collections::{HashMap, HashSet}

Especially when coming from another language where hashmaps and sets are language built-ins with literals provided as part of the language, it can be a little surprising at first that these have to be imported before use. Their usage can be especially common in script-like code, for which effortless usage will be especially helpful.

While a number of third-party hashmaps are in common use (most of which reexport or wrap the std HashMap with a different hasher), bringing the std HashMap into the prelude should not cause issues, since explicit imports will always override prelude imports.

std::{{fs::File}, path::{Path, PathBuf}}

Are very commonly used, not least of which in script-like CLI tools.

std::sync::{Arc, Mutex, RwLock}

Help write threaded code. A potential concern is that async code may want to use async variants of these, although even then the sync Mutex may provide better performance.

Use of alternative implementations (in particular from parking_lot) is fairly common.

std::borrow::Cow

Unlikely to clash with other types, yet this type can be of great help in bridging the gap between references and owned types, particular for String/str and Vec/slice types.

std::cmp::{min, max}

It has come up in discussions that the method forms of min() and max() attached to std numeric types can be confusing to understand. In order to level the playing field with the two-argument free functions of the same names, it would be helpful to import these into the prelude.

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

Other types that have been suggested for inclusion

  • std::sync::{Mutex, RwLock}: async code will regularly want different versions of these types, and there is an ongoing discussion of introducing different versions of these types without poisoning semantics. As such, this does not seem like an opportune time to include these.
  • std::ops::Range (and friends): while adding these to the prelude could make sense in the future, discussion is ongoing about revising Range to fix some long-standing warts. This might result in new Range types being added to std, so adding these now seems like a bad idea.

(See references below.)

Prior art

RFC template Discuss prior art, both the good and the bad, in relation to this proposal. A few examples of what this can include are:
  • For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had?
  • For community proposals: Is this done by some other community and what were their experiences with it?
  • For other teams: What lessons can we learn from what other communities have done here?
  • Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background.

This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages.

Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. Please also take into consideration that rust sometimes intentionally diverges from common language features.

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 editions could add more items and/or remove some.

Previous discussions