Series: The Rust Annals

Vol. I Issue 32 nlopes.dev

Announcing Rust 1.31 and Rust 2018

Rust 1.31 shipped the first Rust 2018 edition, non-lexical lifetimes, stable const functions, and 1.0 releases of Clippy and Rustfmt.

Rust 1.31 delivers the first iteration of Rust 2018, bringing non-lexical lifetimes, a simpler module system, stable const fn, and mature Clippy and Rustfmt releases into one cohesive package. Editions let packages adopt incompatible language changes without splitting the ecosystem, while lifetime and tooling improvements make more valid code compile and make failures easier to understand.

Rust 2018 introduces editions without splitting the ecosystem

Rust 2018 is the release’s main feature and the culmination of three years of work across the language, tooling, documentation, domain working groups, and website. New Cargo projects declare the edition in Cargo.toml>:

[package]
name = "foo"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

[dependencies]

The value may be 2018 or 2015; when the key is absent, 2015 remains the default. Each package chooses its edition independently: Rust 2018 packages can use Rust 2015 dependencies and Rust 2015 packages can use Rust 2018 dependencies. Most language features remain available in both editions; an edition gate is used when a feature requires an incompatibility with Rust 2015.

One big-ticket Rust 2018 feature is non-lexical lifetimes. The borrow checker can now accept valid code where a borrow’s final use occurs before the end of its lexical scope:

fn main() {
    let mut x = 5;

    let y = &x;

    let z = &mut x;
}

Older Rust treated the immutable borrow through y as lasting until the end of main, even though y was never used again, and rejected the mutable borrow. This code now compiles. When the immutable borrow really is used later, the diagnostic identifies that later use instead of merely pointing to the end of the lexical scope:

fn main() {
    let mut x = 5;
    let y = &x;
    let z = &mut x;

    println!("y: {}", y);
}

Previously:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:18
  |
4 |     let y = &x;
  |              - immutable borrow occurs here
5 |     let z = &mut x;
  |                  ^ mutable borrow occurs here
...
8 | }
  | - immutable borrow ends here

With Rust 2018:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:13
  |
4 |     let y = &x;
  |             -- immutable borrow occurs here
5 |     let z = &mut x;
  |             ^^^^^^ mutable borrow occurs here
6 |
7 |     println!("y: {}", y);
  |                       - borrow later used here

In Rust 1.31, non-lexical lifetimes are exclusive to Rust 2018; a backport to Rust 2015 is planned for a later date.

Rust 2018 also simplifies module paths: extern crate is no longer needed in almost all circumstances; macros can be imported with use instead of #[macro_use]; absolute paths begin with a crate name, with crate referring to the current crate; and foo.rs may coexist with a foo/ subdirectory, so mod.rs is no longer required for submodules in a subdirectory. The edition guide contains the full path rules.

Lifetime elision removes boilerplate, and const fn enables compile-time calls

Additional lifetime-elision rules are available in both editions. An anonymous '_ lifetime can show that a type has a lifetime parameter without introducing a name:

impl<'a> Reader for BufReader<'a> {
    // methods go here
}

can now be written as:

impl Reader for BufReader<'_> {
    // methods go here
}

Lifetimes must still be defined on structs, but an applicable outlives bound is inferred:

// Rust 2015
struct Ref<'a, T: 'a> {
    field: &'a T
}

// Rust 2018
struct Ref<'a, T> {
    field: &'a T
}

The explicit : 'a remains permitted. Further elision options were under consideration, but there were no concrete plans.

Rust 1.31 also stabilizes const fn. Such a function can be called normally or in a constant context, where it is evaluated at compile time:

const fn foo(x: i32) -> i32 {
    x + 1
}

const SIX: i32 = foo(5);

This evaluates foo at compile time and sets SIX to 6. For soundness, a const fn must have deterministic output and currently supports only a minimal subset of operations:

  • Arithmetic and comparison operators on integers
  • All boolean operators except && and ||
  • Constructing arrays, structs, enums, and tuples
  • Calls to other const fns
  • Index expressions on arrays and slices
  • Field accesses on structs and tuples
  • Reading constants, but not statics—not even taking a reference to a static
  • & and * of references
  • Casts except raw-pointer-to-integer casts

The allowed operations are expected to grow; the reference gives the full rules.

Clippy and Rustfmt reach 1.0 as the tooling ecosystem matures

Clippy is now available on stable Rust and is considered 1.0, carrying the same lint-stability guarantees as rustc. New lints may be added and existing lints may gain functionality, but lints may not be removed, only deprecated. Code that compiles under Clippy will continue to compile provided no lint is made an error with deny, though it may produce new warnings.

Namespaced tool lints make their origin explicit. Existing Clippy attributes can migrate from:

// old
#![cfg_attr(feature = "cargo-clippy", allow(bool_comparison))]

// new
#![allow(clippy::bool_comparison)]

The new form no longer needs cfg_attr, and warnings help users update to it.

Rustfmt 1.0 guarantees that code formatable today will retain its formatting in the future when default options are used. That compatibility makes cargo fmt -- --check practical in CI.

IDE support now includes options for Visual Studio Code, IntelliJ, Atom, Sublime Text 3, and Eclipse. Work remained unfinished—particularly code completion in RLS-based editors—while support for types, documentation, and go-to-definition was further along.

The release also completes a rewrite of The Rust Programming Language. The online book will be updated with each Rust release, while No Starch Press will periodically incorporate those changes into print editions. The new website has also been promoted to rust-lang.org.

Four domain working groups cover network services, command-line applications, WebAssembly, and embedded devices. Network services was still developing the Futures interface and async/await, so that work had not shipped. The CLI group was developing libraries and documentation, the WebAssembly group had shipped tooling for Rust and WebAssembly, and ARM development for embedded devices now worked on stable Rust.

Standard-library conversions, slice operations, and Cargo workflows expand

The standard library adds conversion implementations and slice operations:

  • u8 implements From<NonZeroU8>, with corresponding implementations for the other numeric types and their NonZero equivalents.
  • Option<&T> implements From<&Option<T>>, with the corresponding mutable-reference implementation.
  • slice::align_to and its mutable counterpart slice::align_to_mut are stable.
  • slice::chunks_exact, its mutable counterpart slice::chunks_exact_mut, and the reverse variants slice::rchunks_exact and slice::rchunks_exact_mut are stable.

Cargo now downloads packages in parallel over HTTP/2. It can also rename a dependency directly in Cargo.toml, avoiding an extern crate foo as baz; declaration:

[dependencies]
baz = { version = "0.1", package = "foo" }

The equivalent table form is:

[dependencies.baz]
version = "0.1"
package = "foo"

In either form, the package named foo is used as baz in source code.

Reproduced from the Rust blog under its publication licence. Typeset in Literata.