Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prelude additions #3090

Closed
wants to merge 8 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions text/0000-prelude-2021.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
- Feature Name: `prelude_2021`
- 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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like a thing that's missing here is a general policy for what we consider prelude-worthy. We don't have one right now, and my gut feeling is that this RFC goes many steps too far in expanding the prelude. Having an actual policy we can verify proposals against seems to be better than having a grab bag of things that people think should be in the prelude.

Copy link
Member

@workingjubilee workingjubilee Feb 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would help future edition changes make choices about the prelude, even if it's just a ponce... a policy only used once, I mean. We should be later able to revisit and see if the rationales were Bad, Actually. ( I am speaking from an assumption that, by sheer weight of inertia, people will think additions are good and subtractions are bad, so there will be a felt need of active justifications to undo a decision but not to make further expansive decisions. )

Copy link
Member

@scottmcm scottmcm Feb 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 to this. Without an agreed philosophy I suspect this will get bogged down substantially by all the details, since every single possible edition addition (doh!) could be its own long conversation.

Operationally, to get it in for the edition, I think it would make sense to focus this to a small number of minimally-controversial changes that very clearly fit the philosophy and need to be edition tied. Then future RFCs can be about specific additions to focus the conversation about them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make sense to focus this to a small number of minimally-controversial changes that very clearly fit the philosophy and need to be edition tied

Strong agree.

I'd imagine this goes along the lines of:

  • Figure out a policy (disallow discussing individual items until we have one, perhaps revert to pre-rfc until we have one)
  • Figure out items which the policy clearly applies to
  • People can disagree with the policy or disagree that the policy applies to an included element.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a trait to the prelude can cause compatibility issues

This is true, but AFAIK T-libs doesn't actually consider that a breaking change (since otherwise every time they added an inherent method, or a new method to a trait, it could break code). Not sure if you want to mention that or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the official terminology is that it's a "minor change": https://rust-lang.github.io/rfcs/1105-api-evolution.html#minor-change-adding-a-defaulted-item

So allowed despite the breakage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's still worth keeping this explanation, and just expanding it to make it clear that while we can make such a change, we may choose not to due to the possibility of breakage.

The explanation should also make clear that many traits we might want to add are especially likely to cause breakage, because they may conflict with similar traits from the crate ecosystem that exist to fill the same need that the standard library trait would.


The expected outcome is to make the newly added traits more accessible to users, potentially stimulating their use compared to alternatives.

# 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module names are snake_case by convention. This should be std::prelude::edition_2021.

Citation: std::collections::hash_map.

You might say, “but v1!”, but I consider that the correct spelling even in snake_case. v_1 would look nasty.

In the very distant past, well before 1.0, there was some momentum for joinedlowerwords crate and module names, as Python does with its packages and modules, but that was steadily dismantled in favour of snake_case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's correct, you're inferring from something that matches [a-z]+_[a-z]+ that the valid handling for numeric tagging must be [a-z]+(_([a-z]+|[0-9]+))* when that does not exclude [a-z]+[0-9]*(_[a-z]+[0-9]*)* which is present in the compiler, extensively, as type and module names. f32, f64, wasm32, aarch64, and x86_64 (a particularly amusing one, on reflection), are all in the compiler as part of module paths, and the compiler already has code that flags based on the name edition2018 already. I won't say edition_2021 is a wrong way to write it according to rustc conventions, just that module names tend to have special pressure on them to be curt and edition2021 seems to be permissible usage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anywhere else we use "edition_2021", "edition 2021", "edition-2021", or "edition2021"? If we do, we should aim to be consistent with that. In Cargo.toml, it's edition = "2021" which doesn't help guide one way or another.


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`:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While most users will never see it, it's still odd to have the modules be named v1 and edition2021. Is it viable to rename v1 to edition2015 and have edition2018 and v1 (for compatibility) as aliases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this to explain the plan a bit more, see also the initial implementation PR. Does that make things sufficiently clear?


- `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}`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like modules are a pretty large change here, so far we've only ever had types, traits, and functions in the prelude, if modules are included then people may think they are crates. This drawback should be called out and addressed.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that e2018 makes all cratenames implicitly present in every scope, I personally am very skeptical of having any other source of implicit inclusion of lowercase symbols.

Currently, the preludes only import one function: core::mem::drop. All other lowercase symbols imported by the preludes are macros, which can only be used with macname! or #[macname], and are not able to visually collide with crate, module, or function names in source. I tentatively would not disagree with placing additional functions in the prelude, but I firmly believe that placing module names in the prelude is a mistake for exactly the reason you outline.

Copy link
Contributor

@mbartlett21 mbartlett21 Feb 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the RFC specify what happens if the module name collides with a crate? There are already mem and fmt crates (that have no reverse dependencies).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the prelude is just a glob import, glob imports have well-defined behavior around this.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbartlett21 it's also possible that other packages explicitly specify the cargo lib.name to be mem or fmt. Not sure if there are any of those, but it's quite likely that this happens in private (as in not released on crates.io) monorepo projects. For example, when I write a binary that uses some ad-hoc procedural macros, I would create a my-codegen package in the same workspace with lib.name as a very short word, e.g. mac or gen. I am unsure whether other people would name their crates mem or fmt in a similar manner.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While adding mem/fmt to the prelude could be pragmatic, I think the drawback of introducing a "separate root" (apparently, effectively) like mem makes the whole thing harder to understand for users. I'd argue for lifting these functions to std::swap etc (as mentioned elsewhere) or other solutions, like no change.

- `std::collections::{HashMap, HashSet}`
- `std::path::{Path, PathBuf}`
- `std::sync::Arc`
- `std::borrow::Cow`
- `std::fs::File`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File doesn't seem very useful without Read or Write. It also has the same problem with async as Mutex and RwLock.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though on that note, having Read/Write in the prelude would make a lot of APIs a lot easier to use. Socket and File IO are frequently annoying to use due to having to import both the traits and the concrete type (File/TcpStream).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would second adding Read/Write to the std::prelude, yes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe Read/Write but actually not the types? Unsure considering the AsyncRead concern, but

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If std::io::Write is added to the prelude, using std::fmt::Write becomes more difficult because of name conflicts. Not sure how many people use that trait, tho.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They could be added as use std::io::{Read as _, Write as _};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the prelude includes std::io::Write, how do I write! using std::fmt::Write to a String?

- `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
[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. 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Speaking from experience from the 2018 edition, this is extremely underspecified.

https://github.com/nikomatsakis/rfcs/blob/edition-2021-or-bust/text/0000-edition-2021.md#tooling-workflow

I'm going to leave a comment there as well, but each edition RFC that has breakages should have a section that:

  • Lists out all the potential breakages, big and small
  • Has an in-depth design for a migration lint that will migrate code such that it compiles on both editions. Make sure to get sign off from the compiler team that such a lint is feasible
  • Optionally has an in-depth design for an idiom lint that will clean up the migrated code (potentially making it compile on the new edition only)

The 2018 edition RFCs tended to handwave about migration, and when the time came to implement it I realized that there wasn't actually a workable plan and as an implementor I had to come up with it at the last moment, trying to quickly build consensus. We should strongly avoid this situation recurring.


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

# 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::<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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

use std::iter::FromIterator;
use std::collections::HashMap;

fn main() {
    dbg!(HashMap::from_iter(vec![(1_u64, 2_u64), (3, 4)].into_iter()));
}

Compiling this gives an error:

error[E0282]: type annotations needed
 --> src/main.rs:6:10
  |
6 |     dbg!(HashMap::from_iter(vec![(1_u64, 2_u64), (3, 4)].into_iter()));
  |          ^^^^^^^^^^^^^^^^^^ cannot infer type for type parameter `S` declared on the struct `HashMap`

error: aborting due to previous error

So no you still need to write HashMap::<_, _>::from_iter(iter) to set S = RandomState.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I my experience collect is very often (guesstimate 90%) used at the end of a chain of method calls that may involve iterator adapters such as map etc. So replacing collect with from_iter would cause the entire chain to be nested in another set of parenthesis and likely indented to another level by rustfmt, which I find reads less nicely. Even with FromIterator in the prelude I think I’d usually stick with collect, either with a turbofish or with a type annotation on the let statement when there’s one (guesstimate 70% of the time, sometimes just because splitting out a long-ish method call chain into a separate statement reads better).

let foo: Vec<_> = bar
    .iter()
    .flatten()
    .chain(baz)
    .cloned()
    .collect();

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't believe that occasionally saving two characters and bringing another problem is big enough advantage to justify importing whole trait in prelude. I have same experience with it being used almost exclusively with combinators.


## `std::{mem, fmt}`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bunch of stuff for these is also applicable to other modules, like io::Result or ops::Add or whatever. Or one could argue for cmp::min to parallel with f64::min.

So I'm not convinced by the justification in here that specifically these two is the right thing to do.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you justify why this means that this module should be added to the prelude? I don't think that an item being imported a lot actually justifies adding it to the prelude. Especially given that we have no way of actually knowing whether "imported a lot" is true. (You can prove it for your code, and maybe for some of the code in public online, but there is so much more Rust code than that.) It is also worth discussing what we think the prelude should even be for. It's hard to discuss which particular items should go in before we've all agreed on that.

In general, I don't think saving people from writing something like use std::mem; is worth the confusion of people trying to figure out whether that name is an external crate or in the prelude.

I am reasonably in favor of adding some common traits like TryFrom/TryInto but types and modules should generally be imported explicitly so people can figure out where the name comes from when they read the code. The reason this is less of a problem for traits is because people generally call the trait methods themselves. They don't commonly refer to the trait name.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can prove it for your code, and maybe for some of the code in public online, but there is so much more Rust code than that.

We can, and in my opinion should (because I think it would be cool as well as educational), grep crater for the proportion of Rust modules that use symbols from the distribution libraries, whether by use or by fully qualified path at the use site.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I guess my point is that not only is that data not complete (because it ignores a huge swath of non-open source code), it also embeds the assumption that usage is sufficient justification. We'd need to define what the prelude is actually for before we get there.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it ignores a huge swath of non-open source code

In the absence of raised evidence to the contrary, I think that assuming closed-source code has the same statistical shape as open-source code, especially given the volume to which crater has access, is a fairly safe thing to do.

A clearer philosophical decision about what the prelude is and should be is certainly worth doing regardless of whether its contents change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the absence of raised evidence to the contrary, I think that assuming closed-source code has the same statistical shape as open-source code, especially given the volume to which crater has access, is a fairly safe thing to do.

I think this is probably not that true, since AFAIK crater has overwhelmingly more library crates than binaries. My experience is that this distribution isn't the same for private code.

That said, I don't know that we can do much here. At the end of the day, we use the data we have access to.


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}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subjectively, this batch feels a lot less "universal" or "general-purpose" to me than the rest. Multi-threading logic might be contained to a small number of modules, so I feel this is getting into diminishing returns territory. And if these items qualify for the prelude, shouldn’t (insert list of favorites) qualify too?

Overall it’d be nice to have statistics about how often various items are imported in a some corpus of code, to better support the choices made by this RFC in what to include or not. Lacking that, I’d err on the side of fewer additions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guide-level explanation only mentions std::sync::Arc; looks like that list is not in sync with the detailed list here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed that when I was updating it, will fix that soon.


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](https://docs.rs/tokio/1.1.1/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use) may provide better performance.

Use of alternative implementations (in particular from parking_lot) is fairly common.
djc marked this conversation as resolved.
Show resolved Hide resolved

## `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](https://internals.rust-lang.org/t/am-i-the-only-one-confused-by-a-min-b-and-a-max-b/13252) 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](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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caveat: The fmt method will collide where the user originally only imported one of the traits.

E.g:

use core::fmt;
use core::fmt::Display;

struct MyStruct(u32);

impl Display for MyStruct {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f) // Just delegate to the inner impl
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you're getting at. That code seems to compile just fine?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@djc

If you have a prelude that then implicitly imports Debug, this will then fail:

// The prelude with Debug:
mod prelude {
    pub(super) use core::fmt::Debug;
}

// Prelude import
use prelude::*;

use core::fmt;
use core::fmt::Display;

struct MyStruct(u32);

impl Display for MyStruct {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f) // Just delegate to the inner impl
    }
}

(playground)

- `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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another drawback: this would be ambiguous with std::io::Error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah calling both types Error was a big flub :/

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lokathor it's a rather common design choice to reuse names in different modules. This means that you have to write io::Error instead of something like IoError, which I actually prefer. Another example is fmt::Write and io::Write. However, I don't like that the write! macro requires importing one of them, but that's another topic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A common misconception. Only std::io and std::fmt do that weird thing where you're supposed to use the module name as a prefix. Every other error in the standard library just has a sensible name that doesn't need a prefix.

Either way, it's a bad plan at this point to put something called Error directly in the prelude.

It's possible that the confusion wouldn't be too high because one is a trait and the others are structs, but better to avoid the possibility.

Copy link
Member

@thomcc thomcc Feb 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not add std::sync to the prelude like it was proposed for std::mem?

I think sync is probably as reasonable as the other proposed modules, but I honestly think you could make that argument for almost every module in the stdlib. I htink for that it's a compelling example of where having some sort of policy here would help.

(EDIT: When I replied to this it was in the thread about the std::sync changes... That said, that thread still seems out of order, so maybe github will fix itself?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think it’s useful to blame users for following the standard library’s pattern of io::Error and fmt::Error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but the standard library has since moved away from that pattern (AllocError, Utf8Error, etc).

Copy link

@CryZe CryZe Mar 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AllocError is not stable and there's an open point about renaming it. Utf8Error is not in an utf8 module, so it's not changing any conventions. I believe I've heard some Error was accidentally stabilized not following that convention though. But I'm pretty sure there's no real precedent for changing that convention.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I find it annoying that I have to write std::sync::atomic::{AtomicU8, AtomicU16} instead of use std::sync::atomic and then atomic::U8 and atomic::U16 (it's even worse to write atomic::AtomicU8 and atomic::AtomicU16). I think it is quite idiomatic in the current state of Rust that we always prefix external items with their module (or crate if it's top-level), similar to the package.Item convention in golang (in which you can't even avoid that).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I kind of wish atomics were just in std::sync::AtomicU8 instead of std::sync::atomic::AtomicU8. I don't think atomic::U8 would be an improvement though, honestly I feel that would be worse. (This isn't on-topic though. These discussions get long enough as is)

I think it is quite idiomatic in the current state of Rust that we always prefix external items with their module

I don't think this bears out in practice. Most of the code I see and interact with isn't written this way. Additionally, autoimport functionality in IDEs doesn't work this way.


## 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](#references) below.)

# Prior art
[prior-art]: #prior-art

<details>
<summary markdown="span">RFC template</summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

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

# 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)