Rust 2018 - Growing in elegance and responsibility
First; I have the utmost respect for the fact that I'm mostly sitting here wishing, while other people do the actual work. But since you asked...
I tend to think of Rust as a language in its teens. We're no longer that child which breaks people's code every other week (like before 1.0), but the language has not really stabilized either. We have many heavy things in flight: GAT, impl Trait, specialisation, NLL, procedural macros, and so on.
And part of growing is learning to say "no". Saying "no" is difficult, because a lot of people might have worked with thinking and evaluating an RFC, believing in it. It's easy to go for the easiest solution that solves a specific problem, without thoroughly consider if there is a more elegant solution that solves a whole class of problems. Even tougher is saying "no" and offering no solution at all (because there is no solution which doesn't also come with heavy drawbacks).
Elegance in this context means to solve as many problems as possible with as few primitives as possible. An example of the opposite is the allocator: we want
alloc_system to depend on
alloc rather than the other way around. The quick, pragmatic solution was to add an unstable feature and some new meta items, whereas a more elegant solution would also have helped the log crate, which essentially suffers from same type of issue.
Another cause for concern is all special stuff that std can do, which other crates can't. We should stick all language items and intrinsics in something even smaller than libcore: maybe core::ops for all operators, core::types for primitive built-in types, and core::ptr for a few basic built-in functions such as memcpy. That's all for a minimal and elegant core, and we should stabilize that interface, so that if someone wants to make a different std for some reason, or make another Rust compiler, they won't have to chase a moving target.
A good tool can be used in the ways the designer expected, but a great tool can be used also in ways that the designer did not expect. Elegance helps with that too.
Judging some of the big in-flight RFCs from an elegance perspective, here's a quick gut feeling from a layman and as such, should be taken with a grain of salt:
- impl Trait - is a no. Because the same functionality is available today using newtypes. Let's make some good macros that can help with making newtypes instead. (Edit: Except for returning closures - but then let's solve the problem of not being able to name a closure instead.)
- NLL - doesn't change the language much in a way that makes the language bigger, so is likely a good thing.
- specialization - from my understanding, this can make it significantly harder to understand what code is actually running. The "default" syntax does not feel natural enough. The rules for what you can and can't do with it seem a bit mind boggling. I'd err on the side of "no" here.
- GAT - Yes! This allows us to express a lot of safe patterns that we can't express today. The syntax fits really well into the language. I hope it lands in 2018!
- Procedural macros - I guess this is a must, to solve all the other things where I hope we decide not to implement a language feature.
Rust is already a big language. We have traits, closures, generics, enums, patterns, modules, lifetimes, meta attributes, coercions, safe/unsafe, macros, and much more. When you program Rust daily, these often come natural, but for a beginner, it's a lot to take in. So when considering a new addition to the language - may it be some syntactic sugar or a bigger language feature - my gut feeling is that we seem to be erring on the side of pragmatism, bolting things on top of the language rather than fitting it in the most elegant way, or even saying "no". (The "catch" sugar is an example where the increased language size is a bigger drawback than the advantages it can possibly deliver.)
Edit: I forgot to add, that growing up also means removing the dust in some corners of the language. E g:
- There are still corner cases where the compiler generates unsound code. Closing them is mostly a matter of priority - somebody needs to do it even if working on new features is more fun.
- We need to decide what to do with Send and Sync. If I'm using a crate, how do I know which types implement Send and Sync, and if the crate author plans to have it stay that way?
Finally, a friend of mine were thinking of starting to use Rust at work and asked me if code written in Rust today would still work in 10 years. If not, he was sceptical to adopt Rust at work. I didn't really know what to answer: even if the Rust code written today would compile 10 years from now, it might very well be that the ecosystem around it has changed so much, that even the tiniest maintenance will lead to massive amounts of code having to be rewritten just to update a few dependencies. That's probably okay for a language in its teens, as long as we're aware of where we are.