The Rust Project in 2021


My last post, The Rust Organization in 2021, was about how things got done. In this post, I want to talk about what I’d like to see get done: the biggest language, library, and compiler features I’d like to see in 2021.

To be sure, this post is more of a “wish list,” in the sense that it may not be possible to do all of it in a year. But these are the most important things I can think of to make progress on.

Language

Generic Associated Types (RFC #1598)

GATs! Everyone’s been wanting these for a while. They unlock a lot of patterns, and are one major prerequisite of async fn in traits. The difficulty has come from implementing them in the compiler, not language design.

I hope to see continued progress on chalk integration, which the Traits Working Group has been making great progress on. In addition to making it easier to support GATs, there are a lot of other benefits to chalk integration. That said, I’ve been saying this for a while, and chalk integration still seems like a fairly big task.

It’s also possible we could have GATs without chalk. There’s been some promising progress here lately, and maybe we should keep pushing on that. Regardless of how it’s done, I’d like to see an almost fully working GATs implementation next year with a clear path to stabilization.

async/await

The async-foundations working group has made a lot of progress this year, including fixing a lot of bugs, including a number of improvements to error messages, opened an RFC for the Stream trait, and drafted one for the #[must_not_await] lint.

I’d like to “supercharge” this work in 2021. I’m hoping that (a) I can personally spend more time on it and (b) more people get involved or ramp up their involvement, including growing more reviewers.

Let’s start by getting the above RFCs merged and implemented! In addition, I’d like to see:

AsyncRead/AsyncWrite

These traits are important for enabling interop among the async ecosystem. However, there hasn’t been broad consensus over what the traits should look like. Most of the context for that is in this post. There were two issues identified here: uninitialized memory, and vectorized I/O.

On the uninitialized memory front, the ReadBuf RFC has been accepted. This gives us a pretty clear idea of what both Read and AsyncRead traits look like with support for uninitialized buffers.

When it comes to vectorized reads and writes, it turns out this issue may not be as important as initially thought. One data point for this is that Tokio is now proposing new AsyncRead/AsyncWrite traits without support for them, citing that layering vectored read/write support on the existing traits has not been all that useful. And even if we do add support for vectored reads and writes to AsyncRead and AsyncWrite, we can forward-compatibly add them as provided methods later.

With these developments, the various traits in the ecosystem have begun to converge. I think we’re closer than ever to reaching consensus on them, and I’d like to push for that in 2021.

Improved documentation and error messages

This isn’t a language feature per se, but it is important. I’d like to see the async book get a lot friendlier to beginners. Some of this work has already begun, and I’m very happy to see it.

I’d also like to continue the work of error message improvements. Making great error messages is a lot of work, but it’s also a great way to get involved in compiler development! If you’re interested, come by the wg-async-foundations meeting.

Const generics

Let’s stabilize a const generics MVP.

Fallible let bindings

Fallible let bindings look something like this:

let Some(foo) = bar else {
    return;
}
// `foo` gets used here

This might look weird at first, but consider that you can already write let bindings with infallible patterns like the following:

enum Foo { Bar(i32) }
fn f(x: Foo) {
    let Foo::Bar(val) = x; // <-- Infallible pattern
    println!("val is {}", val);
}

This feature simply extends this pattern to fallible let bindings, by adding an else block where you exit the current scope if the binding fails (by returning, breaking, or panicking).

Fallible let bindings have the benefit of reducing rightward drift, especially in code that needs several nested if let s today. Personally, I see code like this all the time.1

This feature has been proposed and postponed before, back in 2016 (and been brought up many times since). One of the reasons for postponing back then was that the ? RFC had just been accepted, and things needed time to “bake.” While ? does help in specific situations, especially error handling when returning Result, it’s frustratingly inapplicable to Option unless the code you’re writing happens to be in a function that also returns Option.2

I think it’s time to take this idea off the shelf and add it to the language.

A decision on self-borrowing generators

withoutboats recently theorized that generators, unlike async functions, don’t usually need to be self-borrowing in practice. Their main argument was that self-borrowing isn’t a need that comes up in iterator combinators, whereas it did constantly when writing Future combinators.

I find their logic somewhat convincing, but I’d really like to find out if there are important use cases for self-borrowing generators. If not, it would have pretty big implications for language design questions going forward. I hope we can come to a consensus on this question sometime in 2021.

2021 Edition

Let’s do it. As a quality of life thing I’m really looking forward to implicit named arguments in format strings.

Compiler

Source-based code coverage support

This is well underway, and Rich has done an amazing job implementing it. Branch-level code coverage support is really useful for finding missing test coverage, and I would love to see this stabilized in 2021.

Benchmark suite for rustc generated code

rustc has a great benchmark suite for compiler performance. However, there’s not an official one for the performance of the code compiled by rustc.

In the past we’ve used the compiler performance suite as a proxy for this, because rustc compiles itself, after all. But that isn’t enough, in my opinion. There are a lot of workloads we need to generate code for that don’t look anything like a compiler. For example, compiler has very little to no numerics code or SIMD-ready code, and it uses explicit arena allocation unlike most Rust programs.

Compilers are complicated, and no one understands the entire thing from end-to-end. This is especially true when you consider just how much rustc relies on LLVM to produce optimal code. Few Rust developers are very familiar with LLVM or the development going on there.3 We tend to treat it as a black box, and changes in LLVM can significantly alter the performance of the code we generate without us knowing about it.

lolbench was a great step in this direction. I’d really like to see it get refurbished, adopted into the rust-lang organization, and made to work on multiple platforms.

Library ecosystem

io-uring

I’d really like to see an ecosystem develop around io-uring. I’ve been following ringbahn off and on since it was first released, and it seems more or less like exactly the way I’d approach the problem.4

Still, we need to prove it out using real-world applications. I’m also curious to know if it should influence the design of the AsyncRead and AsyncWrite traits, as this issue talks about.

Thanks to Erick Tryzelaar for reading a draft of this post.


  1. Admittedly, that might be because I write a lot of code in the compiler, and compiler code is especially prone to rightward drift. I’m curious to know how often people want this outside of the compiler. ↩︎

  2. Of course, this is also true of Result: it’s only useful when dealing with Result and your code returns Result. The difference is that in practice, this is less of a concern since error handling is just a pervasive thing you explicitly encode in your function signatures. In contrast, Option comes up everywhere, and that has little to no correlation with whether or not your function returns one. ↩︎

  3. Incidentally, this is something I’d like to change. LLVM has a large developer community, and Rust developers aren’t very well represented there. Plus, it’s rustc’s biggest dependency; we should be aware of what’s going on! ↩︎

  4. io-uring is a new Linux API, but completion queues are not a new concept. I’ve worked with them in the past (in C++, even!) and used similar ownership semantics to make them easy to work with. By this time, Rust was already influencing the way I wrote code in other languages. ↩︎