Rust Features to Look Forward to
!! UNPUBLISHED !! • 9 min read • more posts
Table of Contents
- Introduction
- Language Features
- Standard Library Additions
- Language Redesigns
- Optimizations
- Tooling
- Conclusion
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
&Tin 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
selfparameters 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 underlyingTmight not implementClone) 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
- like kotlin/liquid haskell
- https://github.com/rust-lang/rust/issues/128044
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
#[diagnostic]
Example
fn foo() {
} References
Gen blocks
Example
fn foo() {
} References
Impl trait in type aliases
Example
fn foo() {
} References
Macro metavariable expressions
Example
fn foo() {
} References
Specialization
Example
fn foo() {
} References
- try_as_dyn
- https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md
- min_specialization
- A minimal, sound subset of specialization intended to be used by the standard library until the soundness issues with specialization are fixed.
- https://github.com/rust-lang/rust/issues/31844
Trait aliases
Example
fn foo() {
} References
Try trait
Example
fn foo() {
} References
Type changing struct update
Example
fn foo() {
} References
Standard Library Additions
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
Duration constructors
Example
fn foo() {
} References
Exclusive
Example
fn foo() {
} References
Fallible collection allocation
Example
fn foo() {
} References
f16 and f128
Example
fn foo() {
} References
- was stabilized in LLVM
- happened because of AI lol
- https://github.com/rust-lang/rust/issues/116909
- https://rust-lang.github.io/rust-project-goals/2026/stabilizing-f16.html
Iterator::array_chunks
Example
fn foo() {
} References
Iterator::try_collect
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
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; N]>::try_from_fn
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
Next generation trait solver
Example
fn foo() {
} References
Tooling
cargo-script
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
-
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. ↩