Rust 2018: an early preview

I’m really excited for the new module system! :slight_smile:

3 Likes

Not sure where this should be reported, but I think there’s a typo near the top of https://rust-lang-nursery.github.io/edition-guide/2018/transitioning/ownership-and-lifetimes/lifetime-elision-in-impl.html where impl<'a, 'b> SomeTrait<'a> for SomeType<'a, 'b> is contrasted with impl SomeTrait<'tcx, 'gcx> for SomeType<'tcx, 'gcx>. Somehow SomeTrait<...> went from one lifetime parameter to two lifetime parameters. That is a typo, right?

In the guide, I think the Feature Status page is missing a separation between:

  • Features that actually require the edition = "2018" opt-in in Cargo.toml, for example because they involve a breaking change such a keyword that was previously a valid identifier
  • Other features, which are “only” considered part of Rust 2018 because they affect idioms and are somewhat recent, but are in fact available to 2015-edition-crates, assuming a recent enough compiler.

you no longer need to write extern crate to import a crate into your project.

[…]

Now, to add a new crate to your project, you can add it to your Cargo.toml, and then there is no step two. If you're not using Cargo, you already had to pass --extern flags to give rustc the location of external crates, so you'd just keep doing what you were doing there as well.

If I remember correctly, --extern is not used for crates that are in the sysroot. Is for example extern crate proc_macro; also not needed anymore, and use proc_macro::TokenStream; sufficient for that crate to be linked?

In Rust 2018, you'd write:

fn two_args(foo: &Foo, bar: &'bar Bar) -> &'bar Baz

In other words, you can drop the explicit lifetime parameter declaration, and instead simply start using a new lifetime name to connect lifetimes together.

The example suggests that the lifetime is named after the value-parameter, but the prose suggests it is not. Which is it? In other words, is this valid?

fn two_args(foo: &Foo, bar: &'somethingelse Bar) -> &'somethingelse Baz

Similarly in the “Lifetime elision in impl” page:

    // we can refer to 'arg, rather than conflicting with 'a
    fn bar(&self, arg: &str) -> &'arg str
3 Likes

Is async still planned for Rust 2018? (It’s listed in the Edition Guide table of contents and Feature Status page, so I’m assuming so.) If so, I think it should be listed among the features not included of the preview release, right?

The list in the post above isn’t exhaustive – I’ve added a note about that. However, I believe that we don’t expect async/await to land in time for the initial 2018 edition release, though it should land shortly thereafter. There isn’t an implementation yet in nightly even so it’s somewhat unknown what the exact timeline will be.

It pains me to say, but like many still casually watching / not directly participating, I too feel like things are continuing to churn in the language a bit … beyond reason. Impl Trait, SIMD and slice patterns seem worthwhile (the latter two have been on the books forever). The other bits … I dunno, seem like many are cases of “it worked before”.

I know (and very much respect) that you’re making strong promises in terms of forever-supporting older editions, and “nobody has to use the new stuff”, but I still think there’s value to the great majority of people who will see code out of context without tracking editions in their heads, to keeping the inter-edition delta as small as possible, slowing velocity to changes that are really necessary, keeping the language as close to a single dialect as possible.

I’m not saying this to preserve some special shiny parts of the language I made / liked; much of that was either gone or set in stone by 1.0 anyways. I’m saying it because I want to preserve the exceptionally rare and beautiful success the Rust-you-all-built for 1.0 has experienced so far, and I think it’s hard to see clearly when your day job and/or passionate hobby is hacking on the compiler just how much unnecessary churn does pose a risk to its future. There’s already a ton in the language, and a ton for people to learn / train-on / document / get used to, and a ton of quality work to do on the implementation, integrations and libraries even if no more language features are ever added. Every unnecessary change puts strain on that.

42 Likes

I've read through most of the 2018 features, and one thing I found lacking, which is essential for trying them out, is how to "enable" them and how to "lint" for opportunities to apply them. (For some there is information on how to enable, but for most there isn't.)

For example one of my favorite additions, the dyn Trait one is enabled by default, but you have to also #[warn(bare_trait_objects)] in order to get pointed to where it should be applied.


Regarding the module system changes, I have two "minor issues" with the path clarity feature, namely:

  • I found it to be extremely useful to always use extern cate something, as it provides a nice overview from the source code of what libraries are used;
  • using just crate struct Something (instead of pub(crate) struct Something) has two drawbacks:
    • for once it overloads the "semantic" of the keyword crate; (you can use it (although optionally) in extern crate something, as a "root" for module paths as in use crate::something, then as a visibility modifier crate struct Something, and I'm sure there are other options I don't know of;)
    • having only pub and pub(<something>) as the only visibility specifier made things easier to search for (for example via regular expressions), and it was also more "intuitive" for people not accustomed with all of Rust's "specificities";

Regarding the new ownership and lifetimes features:

  • the anonymous lifetime -- I tried to enable #[warn(elided_lifetimes_in_paths)] to see where it might be applied and I think it'll take me an entire day to correct my code... moreover I don't see exactly what advantages does it bring? (I mean if a function returns a structure which contains borrowed values, then the compiler will complain, thus it will be even more clearer than looking into the definition of that function; moreover it will make refactoring code even more involved when one introduces a new lifetime in an internal structure;)
  • in-band lifetimes and lifetime elision in impl -- I love these feature as it will make my life easier (and my signatures more sane);
  • default match bindings -- i.e. no more random sequences of &, ref and * in my match statements -- love this!

Just wanted to say I also love these three additions, especially the impl Trait which makes it even more easier to create generic functions (and thus hopefully improve performance).

For example I had a lot of functions like pub fn some_functionality <FirstValueRef : AsRef<Value>, SecondValueRef<AsRef<Value>> (first : &FirstValueRef, second : &SecondValueRef) { ... } which I now can write as simple as pub some_functionality (first : &impl AsRef<Value>, second : &impl AsRef<Value>) {...}. Moreover I also have some functions that use plain &Value as arguments, which I can now transform into generics by just "find-and-replace" &Value with &impl AsRef<Value>.

Moreover the addition of &dyn Trait allows me to visually find places in my code where I could use &impl Trait in order to convert them to generics.


All in all great work on getting Rust more "production friendly"!

I welcome any change that reduces the random "character soup" (which as said involves &, *, ref and similar) especially in match statements and working with simple wrappers such as Option<&T> or references.

4 Likes

While the macro imports for module system change is a nice improvement, I would prefer keeping the bang in the import to show that this is not a function.

use bar::baz!;
25 Likes

Hasn’t Swift, which you now work on IIRC, chosen the same path of making refinements even if it means breaking compatibility, though?

And Swift has even more compatibility/stability pressure, given that it’s promoted as “the” way to write iOS software, that is, affecting a multi $100 billion dollar market.

The pace of change and the “swift enhancement proposals” I see (which I understand, you’re not of course influencing yourself completely, its a larger team) seem to go at a much faster pace (and even even more trivial ergonomic or sugary changes).

As someone who spends a lot of time writing C++ code, I would like to point out that raw language power/expressivity (which is what your post seems to focus on) is not everything. Polish and usability matter too, and some of that work must be done at the language level.

That being said, I can certainly understand that one person’s polish is another person’s unnecessary fluff/churn :slight_smile:

11 Likes

With all that trend to simplifications, please don’t convert Rust to Ruby. Rust is more precious, when we talk about programming languages. I love explicit syntax of Rust, wholeheartedly.

6 Likes

I think I’ve found a small syntax inconsistency in the code samples of the Edition Guide concerning in-band lifetimes. Filed an issue for that.

This detail aside, after reading through the guide, I’d say it does a good job of introducing the currently enumerated features, concisely but precisely enough. In particular, I deeply appreciate to have a short reference text on the new modules/path and lifetime situations: I got drowned in all the RFC discussions and was unable to get a clear picture of where this was all going so far.

One scope question though. One of the proposed roles of editions is that “For active Rust users, it brings together incremental changes into an easy-to-understand package”. If so, shouldn’t this Edition guide also mention present some major features which were landed between Rust 2015 and Rust 2018, such as the ? early exit syntax or custom derives?

2 Likes

On this:

No more mod.rs

In Rust 2015, if you have a submodule:

mod foo;

Does that mean you can no longer use foo/mod.rs 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.

1 Like

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:

1 Like

Does that mean you can no longer use foo/mod.rs 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/mod.rs is still usable and allowed, it's just not required. foo/bar.rs would now equate to foo::bar automatically without the need to declare mod bar; inside the old foo/mod.rs.

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.

4 Likes

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.

2 Likes

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

5 Likes

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!};
3 Likes

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.

5 Likes