blog@​arhan.sh:~$

Rust Features to Look Forward to

!! UNPUBLISHED !! • 9 min read • more posts


Table of Contents

Introduction

Right now I think it’s too easy to dislike Rust. I recently got into an argument with a close friend from university after they told me something along the likes of “Rust isn’t good enough.” My stance was true to my past and present belief: you are wrong; Rust has too many upcoming novel features to say so. It was impossible to fault them for sharing a different opinion, though: Rust’s development is increasingly difficult keep up with.

You might expect there to be some central hub of information about “what’s coming next” to Rust—but it turns out this doesn’t exist1 for a pretty reasonable reason: there are over 700 experimental feature flags and dozens of current and future project goals. Concisely and accurately documenting the most important features is a long and arduous process. The time commitment is often not worth it because it can quickly become outdated.

Once in a blue moon, a technical project leader will do exactly that. The exemplar of this is Nikita Popov’s excellent This year in LLVM series; you can tell that a great deal of effort was put into making each article comprehensive. I was recently inspired to do something similar for Rust: aggregate which unknown, niche, or new upcoming features in Rust excite me the most.

For the sake of culling the list, my selection of features follows three filtering criteria:

  • The feature must personally excite me.

    I realize this makes the list inherently biased. I tried my best to take into account GitHub tracking issue reactions as well as opinions from my close friends.

  • The feature must have an accepted RFC, MCP, ACP, UCP, or the equivalent.

    While there are many popular features without an accepted RFC, I maintain a strict policy against including them. Having some form of official endorsement for the specification of this feature reduces the likelihood for its design to change. If you are interested in a more general list of “wanted features in Rust”, check out Rust-for-Linux’s tracking issue.

  • The feature cannot be something you would already expect to compile, at my own discretion.

    In other words, this feature cannot be an obvious improvement of another feature, possibly originally restricted due to implementation difficulty. This includes the entire const generics project, const traits and expression attributes. The intent is to keep the discussion of ideas fresh and interesting.

A few notes about the format for the rest of this article. Every feature is listed alphabetically as a hidden dropdown under one of “Language Features”, “Standard Library Additions”, “Language Redesigns”, “Optimizations”, and “Tooling”. Within each dropdown, the description of the feature follows roughly the same format. First, I briefly summarize the feature and its motivation. Next, I provide an example of the most representative real-world usage of this feature. Finally, I list relevant references.

For full transparency, I shamelessly reuse wording and code examples from official documentation. The Rust language designers have spent significantly more time thinking about how to motivate these features than I; my contribution has been to edit and condense these explanations to keep them somewhat minimal. Of course, as previously mentioned, I make sure to cite my sources.

Language Features


Arbitrary self types

Methods can only be received by value, by reference, or by one of a few blessed smart pointer types from core, alloc and std (Arc<Self>, Box<Self>, Pin<P>, and Rc<Self>). The logical next step is to support custom smart pointers, such as CustomPtr<Self>. This would help with a few things:

  • Automatic code generation tools need to represent foreign language pointers or references somehow in Rust, and often, we want to call methods on such types. However, we can’t always represent these references with &T in methods because they can’t guarantee the aliasing and exclusivity semantics required of a Rust reference.
  • Sometimes the existence of a reference is, itself, semantically important — for example, reference counting, or if relayout of a UI should occur each time a mutable reference ceases to exist. In these cases it’s not OK to allow a regular Rust reference to exist, and yet sometimes we still want to be able to call methods on a reference-like thing.
  • Taking smart pointer types as self parameters can enable functions to act on the smart pointer type, not just the underlying data. For example, taking &Arc<T> allows the functions to both clone the smart pointer (noting that the underlying T might not implement Clone) in addition to access the data inside the type, which is useful for some methods.

Example

#![feature(arbitrary_self_types)]

#[repr(transparent)]
#[derive(Clone)]
/// A C++ reference. Obeys C++ reference semantics, not Rust reference semantics.
/// There is no exclusivity; the underlying data may mutate, etc.
/// (This is an abridged example: a real CppRef type would fully document invariants
/// here.)
pub struct CppRef<T: ?Sized> {
    ptr: *const T,
}

impl<T: ?Sized> Receiver for CppRef<T> {
    type Target = T;
}

// generated by bindings generator
struct ConcreteCppType {
    // ...
}

// all generated by bindings generator; mostly calls into C++
// In this example these are not marked "unsafe" because we do not directly use
// CppRef::ptr in Rust. This example assumes that the corresponding C++ functions
// do not themselves have unsafe behavior and thus can be presented to Rust as safe.
// Safety of FFI is orthogonal to this RFC.
impl ConcreteCppType {
    fn some_cpp_method(self: CppRef<Self>) {
        todo!()
    }
    fn get_int_field(self: &CppRef<Self>) -> u32 {
        todo!()
    }
    fn get_more_complex_field(self: &CppRef<Self>) -> CppRef<FieldType> {
        todo!()
    }
    fn equals(self: &CppRef<Self>, other: &CppRef<Self>) -> bool {
        todo!()
    }
}

// generated by bindings generator
fn get_cpp_reference() -> CppRef<ConcreteCppType> {
    // also calls into C++
    todo!()
}

fn main() {
    // Rust code manipulating C++ objects via C++-semantics references
    let cpp_obj_reference: CppRef<ConcreteCppType> = get_cpp_reference();
    // cpp_obj_reference does not obey Rust reference semantics. Other
    // "references" to the same data may exist in the Rust or C++ domain.
    // But it can effectively be used as an opaque token to pass safely
    // through Rust back into C++
    let some_value: u32 = cpp_obj_reference.get_int_field();
    let some_field = cpp_obj_reference.get_more_complex_field();
    cpp_obj_reference.equals(&get_cpp_reference());
}

References


Associated type defaults

Rust permits associating traits with default consts, fns, but not types. We allow types to have defaults to provide more ergonomic APIs.

Example

#![feature(associated_type_defaults)]

trait Arbitrary: Sized + Debug {
    type Parameters: Default = ();

    fn arbitrary_with(args: Self::Parameters) -> Self::Strategy;

    fn arbitrary() -> Self::Strategy {
        Self::arbitrary_with(Default::default())
    }

    type Strategy: Strategy<Value = Self>;
}

References


AsyncDrop

It is currently impossible to create types (e.g. database connections) that perform async operations on cleanup. We introduce AsyncDrop as an async version of Drop.

Example

#![feature(async_drop)]

impl AsyncDrop for DatabaseConnection {
    async fn drop(&mut self) {
        todo!()
    }
}

References


#[cfg(accessible(..) / version(..))]

The current support for such conditional compilation is lacking. Crate authors often face a dilemma: “Shall I provide more features and implementations for later versions of Rust, or should I stay compatible with more versions of the compiler”.

We introduce #[cfg(accessible($path))] and #[cfg(version(<semver>))] to allow crate authors to extend how many versions back a crate supports within the language itself.

Example

#![feature(cfg_accessible)]

fn make_iter(limit: u8) -> impl Iterator<Item = u8> {
    #[cfg(not(accessible(::std::iter::Flatten)))]
    use itertools::Itertools;

    (0..limit).map(move |x| (x..limit)).flatten()
}

References


Closure lifetime binders

Example

fn foo() {

}

References


Contracts

Example

fn foo() {

}

References


Coroutines
  • open up possibilities for algebraic effects

Example

fn foo() {

}

References


Declarative attribute macros

Example

fn foo() {

}

References


Declarative derive macros

Example

fn foo() {

}

References


Default field values

Example

fn foo() {

}

References


#[derive(CoercePointee)]

Example

fn foo() {

}

References


#[derive(From)]

Example

fn foo() {

}

References


#[diagnostic]

Example

fn foo() {

}

References


Extern types

Example

fn foo() {

}

References


Gen blocks

Example

fn foo() {

}

References


Guard patterns

Example

fn foo() {

}

References


Implied trait bounds

Example

fn foo() {

}

References


Impl trait in type aliases

Example

fn foo() {

}

References


Macro metavariable expressions

Example

fn foo() {

}

References


#[optimize]

Example

fn foo() {

}

References


Return type notation

Example

fn foo() {

}

References


Specialization

Example

fn foo() {

}

References


Trait aliases

Example

fn foo() {

}

References


Try blocks

Example

fn foo() {

}

References


Try trait

Example

fn foo() {

}

References


Type changing struct update

Example

fn foo() {

}

References


Unsafe fields

Example

fn foo() {

}

References


Unsized rvalues

Example

fn foo() {

}

References

Standard Library Additions


Allocator API

Example

fn foo() {

}

References


ascii::Char

Example

fn foo() {

}

References


assert_matches!

Example

fn foo() {

}

References


AsyncIterator

It often desirable to want an async version of Iterator. We introduce AsyncIterator as a core async abstraction, using the design from the Stream trait defined in the futures crate. Compared to Iterator, it allows other tasks to run while it waits rather than blocking between each item yield.

Example

#![feature(async_iterator)]

struct Counter {
    count: usize,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl AsyncIterator for Counter {
    type Item = usize;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        self.count += 1;

        if self.count < 6 {
            Poll::Ready(Some(self.count))
        } else {
            Poll::Ready(None)
        }
    }
}

// A helper function similar to [`StreamExt::next`](https://docs.rs/futures/latest/futures/prelude/stream/trait.StreamExt.html)
fn next<T>(mut iter: Pin<&mut impl AsyncIterator<Item = T>>) -> impl Future<Output = Option<T>> {
    futures::future::poll_fn(move |cx| iter.as_mut().poll_next(cx))
}

#[tokio::main]
async fn main() {
    let mut counter = pin!(Counter::new());
    while let Some(val) = next(counter.as_mut()).await {
        dbg!(val);
    }
}

References


BorrowedBuf

Example

fn foo() {

}

References


ByteString

Example

fn foo() {

}

References


Duration constants

Example

fn foo() {

}

References


Duration constructors

Example

fn foo() {

}

References


Enum variant count

Example

fn foo() {

}

References


Exclusive

Example

fn foo() {

}

References


Fallible collection allocation

Example

fn foo() {

}

References


f16 and f128

Example

fn foo() {

}

References


hash_map!

Example

fn foo() {

}

References


Iterator::array_chunks

Example

fn foo() {

}

References


Iterator::next_chunk

Example

fn foo() {

}

References


Iterator::try_collect

Example

fn foo() {

}

References


minmax

Example

fn foo() {

}

References


MPMC channel

Example

fn foo() {

}

References


Never type

Example

fn foo() {

}

References


OnceCell::get_or_try_init

Example

fn foo() {

}

References


Oneshot channel

Example

fn foo() {

}

References


Phantom variance markers

Example

fn foo() {

}

References


Random data API

Example

fn foo() {

}

References


Restrictions

Example

fn foo() {

}

References


try_as_dyn

Example

fn foo<T: Trait>(t: T) {
    // The branch here is expected to be eliminated when monomorphized.
    if let Some(dt) = try_as_dyn::<_, dyn SubTrait>(&t) {
        // Do something special.  `SubTrait` could even be
        // a marker trait that verifies some invariants required
        // for `unsafe`, or it could supply some supplemental
        // data; you need not use dynamic dispatch specifically
        // (though of course you can).
    } else {
        // Do the thing that works for all `T: Trait`
    }
}

References


<[T]>::split_once

Example

fn foo() {

}

References


<[T; N]>::try_from_fn

Example

fn foo() {

}

References


<[T; N]>::try_map

Example

fn foo() {

}

References

Language Redesigns


Declarative macros 2.0

Example

fn foo() {

}

References


Range

Example

fn foo() {

}

References


Nonpoison mutex

Example

fn foo() {

}

References

Optimizations


Autodiff

Example

fn foo() {

}

References


Next generation trait solver

Example

fn foo() {

}

References


Parallel front end

Example

fn foo() {

}

References

Tooling


cargo-script

Example

fn foo() {

}

References


Custom test frameworks

Example

fn foo() {

}

References


Imports granularity formatting

Example

fn foo() {

}

References

Conclusion

Yeah so I expect the half-life of this list to be one, maybe two years. That’s all I have left to say.

Until next time!

Footnotes

  1. Actually, I was being somewhat dishonest: avid Rustaceans are aware of releases.rs, an automated changelog of the language contributions to the compiler. I’m not the biggest fan of releases.rs because it is difficult to separate features I don’t really care about from the ones that I do.

↑ Scroll to top ↑