Announcing Rust 1.26

May 10, 2018 · The Rust Core Team

The Rust team is happy to announce a new version of Rust, 1.26.0. Rust is a systems programming language focused on safety, speed, and concurrency.

If you have a previous version of Rust installed via rustup, getting Rust 1.26.0 is as easy as:

$ rustup update stable

If you don't have it already, you can get rustup from the appropriate page on our website, and check out the detailed release notes for 1.26.0 on GitHub.

What's in 1.26.0 stable

The past few releases have had a steady stream of relatively minor additions. We've been working on a lot of stuff, however, and it's all starting to land in stable. 1.26 is possibly the most feature-packed release since Rust 1.0. Let's dig in!

"The Rust Programming Language" Second Edition

For almost 18 months, Carol, Steve, and others have been working on a complete re-write of "The Rust Programming Language." We've learned a lot about how people learn Rust since the first book was written, and this version is an improvement in every way.

We've shipped the draft of the second edition on the website for a while now, but with a disclaimer that it was a work in progress. At this point, the book is undergoing some final, minor copy-edits, and being prepared for print. As such, with this release, we are recommending the second edition over the first. You can read it on doc.rust-lang.org or locally via rustup doc --book.

Speaking of print, you can pre-order a dead tree version of the book from NoStarch Press. The contents are identical, but you get a nice physical book to put on a shelf, or a beautifully typeset PDF. Proceeds are going to charity.

impl Trait

At long last, impl Trait is here! This feature has been highly desired for quite a while, and provides a feature known as "existential types." It's simpler than that sounds, however. The core of it is this idea:

fn foo() -> impl Trait {
    // ...
}

This type signature says "foo is a function that takes no arguments but returns a type that implements the Trait trait." That is, we're not going to tell you what the return type of foo actually is, only that it implements a particular trait. You may wonder how this differs from a trait object:

fn foo() -> Box<Trait> {
    // ...
}

While it's true that you could have written this code today, it's not ideal in all situations. Let's say we have a trait Trait that is implemented for both i32 and f32:

trait Trait {
    fn method(&self);
}

impl Trait for i32 {
    // implementation goes here
}

impl Trait for f32 {
    // implementation goes here
}

Consider this function:

fn foo() -> ? {
    5
}

We want to fill in the return type with something. Previously, only the trait object version was possible:

fn foo() -> Box<Trait> {
    Box::new(5) as Box<Trait>
}

But this introduces a Box, which means allocation. We're not actually returning some kind of dynamic data here either, so the dynamic dispatch of the trait object hurts too. So instead, as of Rust 1.26, you can write this:

fn foo() -> impl Trait {
    5
}

This doesn't create a trait object, it's like we had written -> i32, but instead, we're only mentioning the part about Trait. We get static dispatch, but we can hide the real type like this.

Why is this useful? One good use is closures. Remember that closures in Rust all have a unique, un-writable type, yet implement the Fn trait. This means that if your function returns a closure, you can do this:

// before
fn foo() -> Box<Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

// after
fn foo() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

No boxing, no dynamic dispatch. A related scenario happens when returning iterators. Not only do iterators often include closures, but since they nest, you get quite deeply nested types. For example:

fn foo() {
    vec![1, 2, 3]
        .into_iter()
        .map(|x| x + 1)
        .filter(|x| x % 2 == 0)
}

when compiled, gives this error:

error[E0308]: mismatched types
 --> src/main.rs:5:5
  |
5 | /     vec![1, 2, 3]
6 | |         .into_iter()
7 | |         .map(|x| x + 1)
8 | |         .filter(|x| x % 2 == 0)
  | |_______________________________^ expected (), found struct `std::iter::Filter`
  |
  = note: expected type `()`
             found type `std::iter::Filter<std::iter::Map<std::vec::IntoIter<{integer}>, [closure@src/main.rs:7:14: 7:23]>, [closure@src/main.rs:8:17: 8:31]>`

That's a huge 'found type'. Each adapter in the chain adds a new type. Additionally, we have that closure in there. Previously, we'd have had to use a trait object here, but now we can simply do

fn foo() -> impl Iterator<Item = i32> {
    vec![1, 2, 3]
        .into_iter()
        .map(|x| x + 1)
        .filter(|x| x % 2 == 0)
}

and be done with it. Working with futures is very similar.

It's important to note that sometimes trait objects are still what you need. You can only use impl Trait if your function returns a single type; if you want to return multiple, you need dynamic dispatch. For example:

fn foo(x: i32) -> Box<Iterator<Item = i32>> {
    let iter = vec![1, 2, 3]
        .into_iter()
        .map(|x| x + 1);

    if x % 2 == 0 {
        Box::new(iter.filter(|x| x % 2 == 0))
    } else {
        Box::new(iter)
    }
}

Here, we may return a filtered iterator, or maybe not. There's two different types that can be returned, and so we must use a trait object.

Oh, and one last thing: to make the syntax a bit more symmetrical, you can use impl Trait in argument position too. That is:

// before
fn foo<T: Trait>(x: T) {

// after
fn foo(x: impl Trait) {

which can look a bit nicer for short signatures.

Side note for you type theorists out there: this isn't an existential, still a universal. In other words, impl Trait is universal in an input position, but existential in an output position.

Nicer match bindings

Have you ever had a reference to an Option, and tried to use match? For example, code like this:

fn hello(arg: &Option<String>) {
    match arg {
        Some(name) => println!("Hello {}!", name),
        None => println!("I don't know who you are."),
    }
}

If you tried to compile this in Rust 1.25, you'd get this error:

error[E0658]: non-reference pattern used to match a reference (see issue #42640)
 --> src/main.rs:6:9
  |
6 |         Some(name) => println!("Hello {}!", name),
  |         ^^^^^^^^^^ help: consider using a reference: `&Some(name)`

error[E0658]: non-reference pattern used to match a reference (see issue #42640)
 --> src/main.rs:7:9
  |
7 |         None => println!("I don't know who you are."),
  |         ^^^^ help: consider using a reference: `&None`

Okay, sure. Let's modify the code:

fn hello(arg: &Option<String>) {
    match arg {
        &Some(name) => println!("Hello {}!", name),
        &None => println!("I don't know who you are."),
    }
}

We added the &s the compiler complained about. Let's try to compile again:

error[E0507]: cannot move out of borrowed content
 --> src/main.rs:6:9
  |
6 |         &Some(name) => println!("Hello {}!", name),
  |         ^^^^^^----^
  |         |     |
  |         |     hint: to prevent move, use `ref name` or `ref mut name`
  |         cannot move out of borrowed content

Okay, sure. Let's make the compiler happy again by taking its advice:

fn hello(arg: &Option<String>) {
    match arg {
        &Some(ref name) => println!("Hello {}!", name),
        &None => println!("I don't know who you are."),
    }
}

This will finally compile. We had to add two &s, and a ref. But more importantly, none of this was really helpful to us as programmers. Sure, we forgot a & at first, but does that matter? We had to add ref to get a reference to the inside of the option, but we couldn't do anything but get a reference, as we can't move out of a &T.

So, as of Rust 1.26, the initial code, without the &s and ref, will just compile and do exactly what you'd expect. In short, the compiler will automatically reference or de-reference in match statements. So when we say

    match arg {
        Some(name) => println!("Hello {}!", name),

the compiler automatically references the Some, and since we're borrowing, name is bound as ref name automatically as well. If we were mutating:

fn hello(arg: &mut Option<String>) {
    match arg {
        Some(name) => name.push_str(", world"),
        None => (),
    }
}

the compiler will automatically borrow by mutable reference, and name will be bound as ref mut too.

We think this will remove a significant papercut for new and old Rustaceans alike. The compiler will just do the right thing more often without the need for boilerplate.

main can return a Result

Speaking of papercuts, since Rust uses the Result type for returning errors, and ? to make handling them easy, a common pain-point of new Rustaceans is to try and use ? in main:

use std::fs::File;

fn main() {
    let f = File::open("bar.txt")?;
}

This will give an error like "error[E0277]: the ? operator can only be used in a function that returns Result". This leads to a pattern where many people write code that looks like this:

fn run(config: Config) -> Result<(), Box<Error>> {
    // --snip--
}

fn main() {
    // --snip--

    if let Err(e) = run(config) {
        println!("Application error: {}", e);

        process::exit(1);
    }
}

Our run function has all of the real logic, and main calls run, only checking to see if there was an error and exiting. We need to make this second function because main can't return a Result, but we'd like to use ? in that logic.

In Rust 1.26, you can now declare main that returns Result:

use std::fs::File;

fn main() -> Result<(), std::io::Error> {
    let f = File::open("bar.txt")?;

    Ok(())
}

This now works just fine! If main returns an error, this will exit with an error code, and print out a debug representation of the error.

Inclusive ranges with ..=

Since well before Rust 1.0, you've been able to create exclusive ranges with .. like this:

for i in 1..3 {
    println!("i: {}", i);
}

This will print i: 1 and then i: 2. In Rust 1.26, you can now create an inclusive range, like this:

for i in 1..=3 {
    println!("i: {}", i);
}

This will print i: 1 and then i: 2 like before, but also i: 3; the three is included in the range. Inclusive ranges are especially useful if you want to iterate over every possible value in a range. For example, this is a surprising Rust program:

fn takes_u8(x: u8) {
    // ...
}

fn main() {
    for i in 0..256 {
        println!("i: {}", i);
        takes_u8(i);
    }
}

What does this program do? The answer: nothing. The warning we get when compiling has a hint:

warning: literal out of range for u8
 --> src/main.rs:6:17
  |
6 |     for i in 0..256 {
  |                 ^^^
  |
  = note: #[warn(overflowing_literals)] on by default

That's right, since i is a u8, this overflows, and is the same as writing for i in 0..0, so the loop executes zero times.

We can do this with inclusive ranges, however:

fn takes_u8(x: u8) {
    // ...
}

fn main() {
    for i in 0..=255 {
        println!("i: {}", i);
        takes_u8(i);
    }
}

This will produce those 256 lines of output you might have been expecting.

Basic slice patterns

Another long-awaited feature is "slice patterns." These let you match on slices similar to how you match on other data types. For example:

let arr = [1, 2, 3];

match arr {
    [1, _, _] => "starts with one",
    [a, b, c] => "starts with something else",
}

In this case, we know arr has a length of three, and so we need three entries inside the []s. We can also match when we don't know the length:

fn foo(s: &[u8]) {
    match s {
        [a, b] => (),
        [a, b, c] => (),
        _ => (),
    }
}

Here, we don't know how long s is, so we can write the first two arms, each with different lengths. This also means we need a _ term, since we aren't covering every possible length, nor could we!

Speed improvements

We continue to work on the speed of the compiler. We discovered that deeply nesting types was non-linear in some cases, and a fix was implemented. We're seeing up to a 12% reduction in compile times from this change, but many other smaller fixes landed as well. More to come in the future!

128 bit integers

Finally, a very simple feature: Rust now has 128 bit integers!

let x: i128 = 0;
let y: u128 = 0;

These are twice the size of u64, and so can hold more values. More specifically,

  • u128: 0 - 340,282,366,920,938,463,463,374,607,431,768,211,455
  • i128: −170,141,183,460,469,231,731,687,303,715,884,105,728 - 170,141,183,460,469,231,731,687,303,715,884,105,727

Whew!

See the detailed release notes for more.

Library stabilizations

We stabilized fs::read_to_string, a convenience over File::open and io::Read::read_to_string for easily reading an entire file into memory at once:

use std::fs;
use std::net::SocketAddr;

let foo: SocketAddr = fs::read_to_string("address.txt")?.parse()?;

You can now format numbers as hexadecimal with Debug formatting:

assert!(format!("{:02x?}", b"Foo\0") == "[46, 6f, 6f, 00]")

Trailing commas are now supported by all macros in the standard library.

See the detailed release notes for more.

Cargo features

Cargo didn't receive many big new features this release but rather saw a steady stream of stability and performance improvements. Cargo should now resolve lock files even faster, backtrack more intelligently, and require manual cargo update invocations less. Cargo's binary now also shares the same version as rustc.

See the detailed release notes for more.

Contributors to 1.26.0

Many people came together to create Rust 1.26. We couldn't have done it without all of you.

Thanks!