Rust 2018: an early preview


On this:

No more

In Rust 2015, if you have a submodule:

mod foo;

Does that mean you can no longer use foo/ or just that it isn’t required? It’s not clear from the reading. If it is allowed, but, not required, it would be nice if that were 100% clear here.

On another note, is this something that will eventually be deprecated and then removed and will Rustfix, “Fix it” automagically, or, will support always be retained, but, linted against? It would be nice if this were explained a little more.


We kinda set an arbitrary limit on what to include based on features floated at the time the edition was being planned… I since included slice patterns since they were introduced at the same time as other things. It will for sure require much more work than has currently be put into the the edition guide. Not sure we have that bandwidth, but I’ll bring it up :slight_smile:


Does that mean you can no longer use foo/ or just that it isn’t required? It’s not clear from the reading. If it is allowed, but, not required, it would be nice if that were 100% clear here.

foo/ is still usable and allowed, it’s just not required. foo/ would now equate to foo::bar automatically without the need to declare mod bar; inside the old foo/

I don’t believe any of the “old” (2015) syntax will ever be “removed” as it will continue to work for as long as Rust is 1.x (theoretically forever). So even if we’re on edition 2048 the 2015 syntax should still work.


You still need the mod declarations, to be clear. The only thing that changes are what filenames work. Removing “mod” entirely was too controversial.


No, the point of having an edition = "2018" opt-in in Cargo.toml (as opposed to only docs and external communication around the project, that rustc wouldn’t know about) is to be able to make breaking changes. For example, the guide lists async as a new keyword in Rust 2018 so you won’t be able to use it as a variable name like let async = something();, but you could in Rust 2015. (The new raw identifier syntax helps mitigate the keyword thing specifically.)

That said, previous editions will be be supported by newer compiler versions “forever”, so a crate that says on the 2015 edition should still compile in Rust 1.74.0. So in that sense, the support for old syntax won’t be removed from the compiler.


No. Also I feel the same way about Swift. But that’s kinda beside the point.

(Further: those who know me are probably rolling eyes and ignoring this statement as I’m technically conservative and pessimistic by nature. Feel free to ignore the sentiment, sorry.)


I agree with this – in fact, while converting a crate over to the 2018 edition preview, it’s what I tried first, and was confused why it wasn’t working.

More specifically, one use I’d tried was:

use log::{debug!, info!};


In Rust 2015, you can use pub(crate) to make something visible to your entire crate, but not exported publicly:

In Rust 2018, the crate keyword is a synonym for this:

I must’ve missed the RFC for this, but this feels a little special-casey to me - can we also write

pub struct Foo {
    in some_parent_module bar: usize,

I guess the counterargument would be that allowing use within the crate is more common than the other uses, but honestly, pub(crate) really isn’t that much of a burden to type, and is much more self explanatory.


What bothers me quite a bit is that some changes, that I hadn’t heard of before reading this thread/the Edition Guide, seem to optimize for quick writing, while making readability a lot worse.

In particular with in-band lifetimes and lifetime elision in impl if I see something like fn foo(x: &'a Foo) -> &'a Bar I’ll have to check for a lifetime definition of 'a on the impl and, failing that, whether the type that the impl is for is parameterized over a lifetime 'a, to know the scope of 'a.

Similarly T: 'a inference in structs means that I now have to look at each field of the struct to know what the relation between 'a and T is. Granted I’ll probably want to do that anyway, but particularly with multiple lifetime and type parameters I would prefer having this information upfront. As an aside: Does this really work only on structs and not on enums? That would seem like a strange inconsistency.


Ah yes thanks for the correction! This what I was conceptually thinking, but said edition 2048 when I meant Rust 1.99 (or any arbitrarily high number). Appreciated :slight_smile:


I also don’t care much for how internal and external macro imports aren’t being treated uniformly. Couldn’t the crate and super keywords be used to make them uniform? eg. use crate::baz;, use super::foo; (or use crate::baz!;, use super::foo!;)


All imports in 2018 code will be “absolute” – in this case, we’re importing from an external crate so they must start with the crate’s name, not crate which would indicate that the macro comes from the local crate, which isn’t the case.


I am surprised the doc includes stuff that can be done with Rust 2015. Why not limit it to stuff that can only be done with Rust 2018 (i.e. breaking changes)? That would make things more clear.


Yes, the edition system conflates two aspects and gives them the same name:

  • (Minor) breaking changes opted-into by edition = "2018" in Cargo.toml
  • A “rallying point” (term from the RFC) with a number of new features that together change idioms, a good time for people who haven’t looked at Rust in a while to try it again, etc. Something less frequent and so hopefully more exciting than yet another 6-weeks cycle.

This appears to be by design from the core team, despite repeated objections that it will most likely be confusing.


I have a few comments regarding the new edition:

  • never_type: what happened to this feature? I remember that it was being stabilized and then got pulled out / reverted at the last moment due to some regressions for a few crates. I have not seen any news about it since then. I think this is a very important feature for the language and one I am personally looking forward to. I tried going through the relevant github issues/PRs about it to see if there has been any update since it was reverted (any plans to resolve the issues and stabilize it?), but did not find anything. What’s the plan/status?

    • Since the only problem with this feature (at least in my understanding) seems to be that it breaks a few crates, making it backwards-incompatible, perhaps it could just be enabled for the new Rust2018 edition only? People are going to have to fix breaking changes when they decide to opt into the new edition anyway.
  • Please require the bang for macro imports / use statements, to make a clear distinction from other language items. use some_crate::thing; should import a function or type by the name thing, not a macro. use some_crate::thing!; should import a macro, not a language item. I feel that macros are sufficiently different from other parts of the language that such a distinction is important. It makes everything much clearer.

  • I prefer pub(crate) over crate as a visibility specifier. I think it is much clearer and less confusing. Also, I don’t remember seeing discussion about this. This feels like yet another change being added to the language without sufficient discussion. I have a feeling that this might lead to controversy similar to what happened after impl Trait in argument position was stabilized and people felt like they had no say.

    • There is a nice consistency about pub being the single keyword used to expand the visibility of things beyond private. It also has the benefit of making things more greppable / easier to search. Right now, you can just search for pub to find any non-private items.
  • I also personally dislike in-band lifetimes and lifetime elision. I think it makes the readability of the code worse, as it is more difficult to see where lifetimes come from / where they are defined. I like the current explicitness.

  • dyn Trait: apparently using the old bare Trait syntax will only be a warning in the new edition, not an error? I understand that this is widely-used syntax and will require lots of changes to lots of crates, but it seems like something trivially automatable (with e.g rustfix). I’d have liked to see a single consistent syntax being enforced.

Other than my critcisms above, I love pretty much all the other changes with the new edition. Rust definitely feels like it is becoming even better. Big thanks to everyone involved for your work!


I am also looking forward to the never type, but it is not something that can only exist in some editions. If a function from another crate (which might be in a different edition) returns a Result<Foo, !> value, you have thot value.

Stabilization is blocked on cases let _: Box<std::error::Error> = Box::new(!) compiling correctly What should happen is that new returns a Box<!>, which is then unsized to a trait object. (Error is a trait that ! implements.) What happens today is that ! is coerced to Error (which it can because it can coerce to anything, since it doesn’t have any value) and then Box::<T>::new cannot be called because it requires T: Sized, but Error: !Sized since it is a trait.

This in turn is blocked on people knowing the compiler well enough to change how coercion works being available, but those same people are also in high demand for things like NLL.


I agree that pub(crate) is fine and that changing it to crate feels a bit gratuitous, when it has already been around for a year.


Quoting the edition-guide:

Therefore, if you’re using Rust 2015, and one of your dependencies uses Rust 2018, it all works just fine. The opposite situation works as well.

Just to be clear: most features will be available on all editions. People using Rust 2015 will continue to see improvements as new stable releases are made.

I’m having a hard time wrapping my head around how this will work. If a new feature is added in rust version X (where X is from the Rust 2018 edition), how will the rust compiler in version X-1 (where X-n is from the Rust 2015 edition) know how to compile this code?

To test this out, I created a dependency that opted-in to rust 2018 (via the instructions in the edition-guide), and then tried to depend on it from Rust 2015 (rustc 1.26.1) and I got the following error:

error: failed to load source for a dependency on `mydep`     

Caused by:                                                                                                                                                                      
  unknown cargo feature `edition`

Given the first thing I quoted from the edition-guide, I thought this might have [magically] worked. I suspect I have some fundamental misunderstanding (and so I’d like to see the edition-guide do a better job at explaining these compatibility rules)


Editions are separate from versions. A given version of Rust (e.g. 1.34) has a set of language features it supports and a latest edition it supports, but it also supports all earlier editions. So you can still compile a 2015-edition crate with 1.34. (But not future editions the will/did not exist yet when 1.34 will be/was released.)

(This error message means that 1.26 does not know about editions at all, or viewed differently that it only supports crates that are implicitly in the 2015 edition since that’s the default.)


Thanks. that helps my understanding. So in addition to waiting for the first compiler version to support the 2018 edition, we are also waiting for the first compiler to support the 2015 edition (and we can think of all the compilers currently out in the wild as having support from some implicit “zero edition” which happens to be identical to 2015)