Rust: Epoch.Next Tracking Issue

Created on 20 Dec 2017  Â·  25Comments  Â·  Source: rust-lang/rust

What is this issue?

Have you noticed that there is a lot going on with Rust these days? Over the last year, there’s been a large push to design a number of tweaks to the Rust language, all with the goal of improving ergonomics and productivity for new and experienced users alike. This issue will help you see what changes are in the works and give you tips for trying them out, giving feedback, or helping to get them implemented.

Legend

✅ Available in Nightly in “code complete” form
đź’› Available in Nightly but design not fully realized
đź”´ Not yet available in Nightly

Ownership



âś… Simplifying how match and borrowing interact

| Feature gates | #![feature(match_default_bindings)] |
| -------------- | ---------------------------------------------- |
| Tracking issue | https://github.com/rust-lang/rust/issues/42640 |

Matching over borrowed data today in Rust requires a combination of * operators, & patterns, and ref bindings. The match_default_bindings feature replaces them with a simpler system; when matching against a value of type &T, you can simply ignore the & and give a pattern that matches the underlying type T. Any bindings in that pattern will be made into references themselves.

Example

Simple example matching an &Option<String>::

#![feature(match_default_bindings)]

fn print_opt_string(x: &Option<String>) {
    // Here, `x` has type `&Option<String>`...
    match x {
        // ...but we give a pattern that matches `Option`,
        // ignoring the `&`. As a result, the `y` variable
        // gets the type `&String`.
        Some(y) => println!("{}", y),
        None => println!("none"),
    }

    // Equivalent to the following in today's Rust:
    // match x {
    //     &Some(ref y) => ...
    //     &None => ...
    // }
}

fn main() {
    print_opt_string(&Some("foo".to_string()));
}

What’s left to be done?

Not much. There are a few corner cases that we might want to fine-tune: see the tracking issue for the full details.



âś… Easier borrowing (a.k.a. NLL)

| Feature gates | #![feature(nll)] |
| -------------- | -------------------- |
| Primary tracking issue | https://github.com/rust-lang/rust/issues/43234 |
| Other tracking issues| rust-lang/rust#44100 |

The compiler is now able to understand control flow much more deeply when deciding what paths are borrowed at any particular point in the program. These changes eliminate a large number of confusing borrowing errors, particularly those errors that are due more to imprecision in the analysis.

Examples

Borrow lifetimes are no longer tied to lexical scopes.

#![feature(nll)]

struct Data {
    value: u32
}

impl Data {
    fn is_odd(&self) -> bool {
        (self.value & 1) != 0
    }
}

fn main() {
    let mut vec = vec![Data { value: 1 }];

    // In today's Rust, storing `&vec[0]` into a variable
    // would cause the borrow to last until the end of
    // the enclosing block. But with `nll` enabled,
    // the borrow only lasts until the final use of
    // `first`.
    let first = &vec[0];
    if first.is_odd() {
        // This means that `vec` can be mutated here.
        vec.push(Data { value: 22 });
    }

    // Today's Rust, as least if you want to keep
    // the variable `first`:
    //     let is_odd = {
    //         let first = &vec[0];
    //         first.is_odd()
    //     };
    //     if is_odd { .. }
}

Invoking &mut self methods no longer requires “unnesting”:

#![feature(nll)]

struct Counter {
    data: u32
}

impl Counter {
    fn get(&self) -> u32 { self.data }
    fn set(&mut self, value: u32) { self.data = value; }
}

fn main() {
    let mut c = Counter { data: 0 };

    // In today's Rust, this would get a borrow error,
    // because we first borrow `c` (as the receiver to `set()`)
    // and then afterwards attempt to evaluate `c.get()`.
    c.set(c.get() + 1);

    // Today's Rust:
    //     let tmp = c.get() + 1;
    //     c.set(tmp);
}

What’s left to be done?

There is still some engineering effort remaining. For example, some of the error messages are not yet particularly user friendly. In addition, we need to tighten some of the corner cases described in the various RFCs (these are not soundness issues per se, but rather cases where we fear that the analysis may not be forwards compatible with future changes we have in mind).

We will be transitioning to the new borrow system, but slowly. We plan to run a “trial period” where we gather feedback and try to find bugs. We also need to do a “warning period” before enabling the new borrowing system by default, as the new system fixes a number of bugs where the compiler incorrectly accepted illegal code (the new implementation is believed to be significantly more robust).



đź’› In-band lifetimes

| Feature gates | #![feature(underscore_lifetimes, in_band_lifetimes)] |
| -------------- | ------------------------------------------------------ |
| Lints | #![warn(single_use_lifetime, elided_lifetime_in_path)] |
| Tracking issue | https://github.com/rust-lang/rust/issues/44524 |

The current rules that govern explicit lifetime names have some confusing edge cases. These can lead to surprising errors that are hard to diagnose. In addition, the existing annotations can be tedious to supply and get right. The in-band lifetimes RFC aims to adjust the rules to make for a smoother experience overall.

Warning: while the major features of this RFC are in place, some of the lints are not yet fully implemented. This

The highlights:

  • In functions, you can now use '_ to indicate an “anonymous lifetime” that you do not care to give a name. This is primarily intended for use with lifetime-parameterized structs; for example, one can now write Foo<'_>, whereas before it was common to either write Foo (which obscured the fact that a lifetime was present) or to introduce a one-off name like Foo<'a> (where 'a is never used anywhere else).

    • In fact, lifetime parameters will be required (enforced via lint) on structs and enums in all contexts — in other words, one should not write just Foo is Foo has a lifetime parameter — but you can use Foo<'_> if the specific value is not important.

  • In functions and impls, you can now leave off explicit lifetime declarations like the <'a> in impl<'a>. Instead, the intention is that simply annotate the lifetimes that must be the same by giving them explicit names, and use '_ for lifetimes that are not required to match against other lifetimes.

Examples

TBD

What is left to do?

Some pieces of this design are not yet implemented:

  • You cannot yet use '_ or elide lifetimes in impls (issue #15872)
  • Some of the lints that will guide users down the “happy path” are not yet fully implemented:

    • Lint against single-use lifetimes (instructing users to prefer '_) (issue #44752)

    • Lint against “silent elision” in structs (e.g., Foo instead of Foo<'_>) (issue #45992)



đź”´ Infer T: 'a outlives requirements on type definitions

| Feature gates | N/A — not yet implemented |
| -------------- | ---------------------------------------------- |
| Tracking issue | https://github.com/rust-lang/rust/issues/44493 |

Explicit T: 'x annotations will no longer be needed on type definitions. We will infer their presence based on the fields of the struct or enum. In short, if the struct contains a reference (directly or indirectly) to T with lifetime 'x, then we will infer that T: 'x is a requirement:

struct Foo<'x, T> {
  // inferred: `T: 'x`
  field: &'x T
}

Explicit annotations remain as an option used to control trait object lifetime defaults, and simply for backwards compatibility.

Examples

Coming soon =)

What’s left to be done

Everything

The Trait System



âś… impl Trait and dyn Trait

| Feature gates | #![feature(universal_impl_trait, conservative_impl_trait, dyn_trait)] |
| -------------- | ----------------------------------------------------------------------- |
| Tracking issue | #34511 and #44662 |

impl Trait is a long awaited feature that allows one to describe types by the traits that they implement. For example, a function like fn foo(args: impl Iterator<Item = u32>) declares a function foo that takes an iterator of u32 of argument; impl Trait can also be used in return position. Currently, impl Trait is limited to function signatures and cannot be used in traits or trait impls. In the future, we aim to support the notation in more places.

dyn Trait is a simple syntactic change: whereas a trait object type used to be written as something like &Write (where Write is a trait), it is now preferred to write &dyn Write, which helps to make clear that (a) Write is a trait and (b) that method calls on Write will employ dynamic dispatch.

Together, these two features help to both grow expressiveness, and to address a common point of user confusion about the role of traits and types. In particular, when using these keywords, a trait is never used directly as a type; rather one uses the impl or dyn keyword to select how to use

Examples

Using impl Trait in argument position:

#![feature(universal_impl_trait, conservative_impl_trait, dyn_trait)]

fn apply(c: impl Fn(u32) -> u32, arg: u32) -> u32 {
  // equivalent to `fn apply<F>(c: F, arg: u32) where F: Fn(u32) -> u32`
  c(arg)
}

fn main() {
  println!("{}", apply(|x| x * 2, 22)); // prints 44
}

Using impl Trait in return position:

#![feature(universal_impl_trait, conservative_impl_trait, dyn_trait)]

fn even_numbers() -> impl Iterator<Item = u32> {
  (0..).map(|x| x * 2)
}

fn main() {
  for x in even_numbers().take(5) {
    println!("{}", x);
  }
}

Using dyn Trait:

#![feature(universal_impl_trait, conservative_impl_trait, dyn_trait)]

fn apply(c: &dyn Fn(u32) -> u32, arg: u32) -> u32 {
  c(arg)
}

fn main() {
  println!("{}", apply(&|x| x * 2, 22)); // prints 44
}

What’s left to be done?

There are still a few outstanding questions about the syntax, notably the precedence of impl Trait. You can get the full details at the tracking issues:



âś… Closures implementing Clone or Copy

| Feature gates | #![feature(copy_closures, clone_closures)] |
| -------------- | ---------------------------------------------- |
| Tracking issue | https://github.com/rust-lang/rust/issues/44490 |

Closures are now copyable / cloneable if the variables that they capture are copyable / cloneable. Note that non-move closures often borrow captured variables instead of taking ownership of them, and hence a closure may be Copy even if some of the variable that it uses are not (because it only requires a shared reference to them).

Examples

(Try it on play.)

#![feature(copy_closures, clone_closures)]

fn main() {
    let v = vec![1, 2, 3]; 

    // this closure captures `v` by shared reference:
    let v_len1 = || v.len();

    // therefore, it is `Copy`:
    let v_len2 = v_len1;

    // and naturally also `Clone`:
    let v_len3 = v_len1.clone();

    assert_eq!(v_len1(), v.len());
    assert_eq!(v_len2(), v.len());
    assert_eq!(v_len3(), v.len());
}

What’s left to be done?

Gain experience.



đź”´ Trait aliases

| Feature gates | #![feature(trait_alias)] |
| -------------- | ---------------------------------------------- |
| Tracking issue | https://github.com/rust-lang/rust/issues/41517 |

Trait aliases allow you to make aliases for common used combinations of trait bounds and where-clauses, much like a type alias lets you have an alternate name for a commonly used type.

Example

(Since this feature is not fully implemented yet, example will not actually work.)

#![feature(trait_alias)]

trait SendWrite = Write + Send + Sync;

fn foo<T: SendWrite>(t: &T) {
    // ^^^^^^^^^^^^^ equivalent to `T: Write + Send + Sync`.
}

What’s left to be done?

Quite a bit. Some of the parsing and infrastructure work landed in nightly, but the semantics are not yet implemented.



đź”´ Generic associated types

| Feature gates | #![feature(generic_associated_types)] |
| -------------- | ---------------------------------------------- |
| Tracking issue | https://github.com/rust-lang/rust/issues/44265 |

Generic associated types allow associated types defined in traits to take lifetime or type parameters. This allows for common patterns like an Iterable trait which are currently quite difficult to do.

Example

(Since this feature is not fully implemented yet, example will not actually work.)

use std::vec;

trait Iterable {
    type Iterator<'a>;
}

impl<T> Iterable for Vec<T> {
    type Iterator<'a> = vec::Iter<'a, T>;
}

What’s left to be done

The parsing and various bits of the semantics are implemented, but there is still more to come. See the tracking issue.

Error Handling



âś… ? applied to Option and other types

| Feature gates | #![feature(try_trait)], but not needed to use ? with Option |
| -------------- | ----------------------------------------------------------------- |
| Tracking issue | https://github.com/rust-lang/rust/issues/42327 |

You can now use the ? operator in functions that return Option, not just Result. Furthermore, through the (as yet unstable) Try trait, you can extend ? to operate on types of your own.

Example

/// Returns `None` if either `a` or `b` is `None`,
/// but otherwise returns `a + b`.
fn maybe_add(a: Option<u32>, b: Option<u32>) -> Option<u32> {
    Some(a? + b?)
}

What’s left to be done?

Gain more experience with the Try trait.



âś… ? in main()

| Feature gates | #![feature(termination_trait)] |
| -------------- | ---------------------------------------------- |
| Tracking issue | https://github.com/rust-lang/rust/issues/43301 |

You can now give main() alternative return types, notably including Result types. This enables the use of the ? operator within main(). The goal is to support ? also in unit tests, but this is not yet implemented.

Note: Implemented in PR https://github.com/rust-lang/rust/pull/46479, which has not yet landed.

Example

use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut file = File::create("foo.txt")?;
    file.write_all(b"Hello, world!")?;
}

What’s left to be done?

Once PR https://github.com/rust-lang/rust/pull/46479 lands, you should be able to use examples involving main. But the full design also allows for writing unit tests (#[test] fns) with Result return types as well.

The Module System



âś… Nested groups in imports

| Feature gates | #![feature(use_nested_groups)] |
| -------------- | --------------------------------------------- |
| Tracking issue | https://github.com/rust-lang/rust/issues/44494 |

You can now nest “import groups”, making it easier to import many names from a single crate without breaking things into multiple use statements.

Example

(Try it on play)

#![feature(use_nested_groups)]

use std::sync::{
    Arc, 
    atomic::{AtomicBool, Ordering},
};

fn main() {
    let c = Arc::new(AtomicBool::new(true));
    let v = c.load(Ordering::SeqCst);
    println!("v={:?}", v);
}

What’s left to be done?

Gain experience, find bugs in the implementation.



đź’› Clarify and streamline paths

| Feature gates | #![feature(crate_in_paths, crate_visibility_modifier, non_modrs_mods)]
| -------------- | ---------------------------------------------------------------------------------------------------- |
| Lints | #![warn(unreachable_pub)] |
| Tracking issue | https://github.com/rust-lang/rust/issues/44660 |

These changes seek to clarify and streamline Rust's story around paths and visibility for modules and crates. That story will look as follows:

  • Absolute paths should begin with a crate name, where the keyword crate refers to the current crate (other forms are linted, see below)
    extern crate is no longer necessary, and is linted (see below); dependencies are available at the root unless shadowed.
  • The crate keyword also acts as a visibility modifier, equivalent to today's pub(crate). Consequently, uses of bare pub on items that are not actually publicly exported are linted, suggesting crate visibility instead.
  • A foo.rs and foo/ subdirectory may coexist; mod.rs is no longer needed when placing submodules in a subdirectory.

Example

What’s left to be done?

C-tracking-issue T-core WG-epoch metabug

Most helpful comment

I've updated the previous green heart (💚 — "Available in Nightly in “code complete” form") to a checkbox (✅), which appears as a green square for me.

All 25 comments

the tracking issue link for "Clarify and streamline paths" links to the issue for NLLs.

Fixed, thanks!

There is a copy and paste slip in the box at the top of the "impl Trait and dyn Trait" section - the tracking issues should be #34511 and #44662 rather than #42640.

Looking forward to those ownership improvements :)

@glyn Fixed, thanks!

Should specialization and macros 2.0 be tracked here?

@nikomatsakis - Not sure if anyone else mentioned it, but NLL says "This feature is blocked on #46862 landing." which has now landed.

@jonathandturner updated, though nightly still does seem to have the changes.

@durka

Should specialization and macros 2.0 be tracked here?

I don't think either of those are on track to be part of the next epoch. Specialization no, because it is not on track -- design is still very much in flux. I feel similarly about Macros 2.0 -- for example, there is no RFC or description of the hygiene system.

Note though that not being "part of the epoch" won't necessarily delay features from being available. The epoch is more of an end-point: basically a way to say "hey, a coherent set of features and associated tooling and documentation has landed -- you should give Rust another look!".

Awesome to see so much great work in this!

Where does the work on const items, and constants in types land in respect to the Epoch system?

The description of the impl Trait example looks incorrect:

declares a function foo that takes an integer of u32 of argument

What about const generics? Currently for me it's the most expected feature and I think it will be good for marketing to include it into the new epoch features list.

@newpavlov New features should only be part of an epoch if they require a change which is not backwards compatible. Otherwise the feature should land normally so that people aren't forced to update their code for the new epoch just to gain access to that feature. Shoving something into an epoch just for the sake of marketing is a bad idea.

@retep998
I was under impression that epochs will be also used to highlight the most important additions to the language since the last epoch and looking on the list in the OP most (or even all?) features look backwards compatible to me.

I wouldn't consider impl Trait green yet. The inability to return impl Trait types in trait methods is a huge limitation.

46479 (? in main) has landed

Can we change the legend/keys to be colorblind friendly (different shapes for each key)? I can't tell the difference between the two hearts.

I've updated the previous green heart (💚 — "Available in Nightly in “code complete” form") to a checkbox (✅), which appears as a green square for me.

Can we change the legend/keys to be colorblind friendly (different shapes for each key)? I can't tell the difference between the two hearts.

Sorry about that -- I was actually trying to be colorblind friendly by having at least have the red/green keys have a distinct shape, but I guess I should have further. =)

@nikomatsakis - any guidance on what makes the epoch.next list? I know some folks are going to ask about features like const generics and no doubt others.

Const generics seems like a pretty big feature to try to finish this year and get it to stable...

Should async/await be added to this list, given the recent design breakthroughs (described in the @withoutboats blog posts) and the inclusion in the 2018 Roadmap?

We have to figure out just how to manage this issue -- I wouldn't take this as an exhaustive list just now.

Closing this, seems out of date

Was this page helpful?
0 / 5 - 0 ratings