Salt Documentary

What I would like to see in Rust in 2019

published on in Discussion
tagged Programming, Rust

The Rust maintainers recently asked the community what they would like to see happening in Rust, in 2019. Many people have stated that Rust could use a fallow year. I generally agree, but there are a few changes to the language I consider important/useful enough to implement despite the proposed moratorium on features.

The Type System

It does not befit a safety-conscious programming language to make the type system painful to use. Unfortunately, there are two cases where Rust does this: enumerations and newtypes, both of which are very handy tools for enforcing correctness in a program more thoroughly.

Anonymous Enumerations (a.k.a. Coproducts)

There have been several RFCs proposing some variation of this idea, but none of them really caught on, so I'll describe my personal interpretation of the concept (a standalone article on this is in the works as well).

Let's start with a regular enum for the sake of comparison:

enum Attempt<S, E> {
    Success(S),
    Failure(E)
}

We have to use a match statement (or something comparable) to destructure a value of this type:

match my_attempt {
    Success(s) => println!("success {}", s),
    Failure(f) => println!("failure {}", f)
}

Now let us define the same as an anonymous enum. For the lack of a better syntax, I choose and to represent a coproduct:

type AnonymousAttempt = ⟨T, E⟩;

The difference becomes evident when looking at the destructuring expression for this one:

println!("*shrug* {}", my_anonymous_attempt);

That's right, we do not need to destructure it.

The second difference is how they are constructed. Given an instance t of T, how do we construct the respective type?

process_attempt(Attempt::Success(t));
process_anonymous_attempt(t);

As you can see, the anonymous enum basically says any value of these types can appear in this spot. This is very useful in certain conditions:

These situation are more common than you may think. In fact, when looking at a given project, I probably can easily spot two to three types that could be much cleaner, if only they were coproducts.

Speaking of cleaner code…

A (subjectively) better syntax for the newtype idiom

How do we represent velocity? With an f64. How do we represent acceleration? With an f64. Is it therefore legal to assign a velocity value to an acceleration variable? No, of course not. Tuple structs to the rescue.

struct Velocity(f64);
struct Acceleration(f64);

…what? You want to do math with that? Lol have fun with impl Add<Velocity> for Velocity { … } and its many friends. Would it not be nice to instead do something like this:

newtype Velocity = f64;
newtype Acceleration = f64;

With this imaginary keyword, you would tell the compiler that your type is an f64 with all the trait impls associated with a garden variety f64, but not quite the same as all the others. The way I imagine this to happen is that for a type U and every trait T<…,U,…> implemented for U, the newtype N = U automatically inherits an implementation T<…,N,…> with exactly the same code as the original.

A nice bonus would be to allow (1) assignment of newtype values to variables of the base type, and (2) assigment of base type literals to newtype variables:

/* base type variable ← newtype value */
let speed: f64 = my_velocity; // OK
/* newtype variable ← base type literal */
let accel1: Acceleration = 0.0; // OK
let float: f64 = 0.0;
let accel2: Acceleration = float; // NOT OK

Rule (2) is for ergonomic initialisation; (1) exists because at some point, you will have to interact with third-party libraries that know nothing about your beautiful newtype landscape.

Before you ask

Yes, I am aware of derive_more. The problem with derive_more is that the new types are still tuple structs, which are clunky. As it stands right now, I often decide against them because they are too tedious to interact with, and derive_more does not change that.

Practical Concerns

Assuming these changes to the type system, I would be quite a bit happier with Rust for day-to-day programming activity (read: userspace development). However…

I'd like to write a kernel in stable Rust, and Rust only

…and I can do it in BetterC, today! Inline assembly, #[naked] functions and other features useful for programming outside the cosy userspace environment are still feature-gated. Rust is often called a systems language and many people believe that the language is successful because it gives C programmers a viable and safe alternative. I totally agree, but let us not stop there.

Take a look at D and, in particular, the aforementioned BetterC. It absolutely excels at low-level programming. To give you a taste, in BetterC you can:

Let us draw inspiration from BetterC. I don't expect to recreate wrap_ms because it uses mixin amongst other things, but everything else on this list is a must-have for Rust. 2019 would be a good year to hammer these features out.

Non-technical enhancements

A common point of critique against Rust is the standard library, which is commonly perceived as too small—a sentiment to which I very much agree. I'd ike to propose what I consider the most practical way to solve the problem.

The Rust Distribution

Sites like awesome-rust and libs.rs strive to be comprehensive directories of Rust libraries for various purposes. They are an attempt to curate the ecosystem and help people find high-quality tools for whatever job they're doing. That's a good start, but doesn't change the fact that these libraries are developed by third parties, which is a non-starter for many companies. They often have to run formal verification on all their dependencies or straight up disallow the use of third-party software except for the standard libraries.

What I would love to see in 2019 is the formation of a Rust Distribution Workgroup. The purpose of this WG would be to cooperate with the authors of common, famous and recommended libraries to bring specific versions up to a standard considered acceptable for official release. These versions would have to be verified by the WG and come with comprehensive documentation. Good initial targets for this WG would be libraries for:

I'm not sure how often a new version of the Rust Distribution would be released, but the idea is that the libraries included would be considered an extended standard library one can use without having to worry about compatibility and correctness.

Now, the reason Rust does not have a comprehensive standard library is the fear that it could become bloated over time because of backwards compatibility guarantees. The distribution would tackle this problem by adhering to a formal deprecation process: With each release of the distro, maintainers have the opportunity to mark parts of their libraries as deprecated and delete code that has already been deprecated before. This of course means people are going to be stuck on older versions of the distribution, especially if there are many releases over a short timespan. Therefore, it would be necessary to backport security updates and bugfixes to older versions.


Before I conclude this list, I would like to address one more thing that, in my opinion, causes some problems for the language and community.

Marketing

The community is, naturally, trying to present Rust as a polished language, ready for deployment in your industrial-grade project. That is, in itself, a good thing. However, haphazardly announcing 1.0 versions because "we need great tooling" is not. Especially when there is a backlog of critical problems in the repository. Neither is it good to claim that Rust has a huge standard library like Go because one can easily install crates. I have seen a lot of similar statements in 2018, but thankfully, most of them were outed by the community as the marketing fluff they are.

I would like to ask you all to do your best in 2019 to not exaggerate what Rust is or can do, to be realistic and honest when talking about the language, and to continue standing up against the Rust Evangelism Strike Force.

Thank you for reading.