The Last Thing Rust Needs

A #Rust2019 blog post

In 2014, world-renowned C++ expert Scott Meyers was invited to DConf, Facebook's conference on the D programming language. D is a modern, multi-paradigm systems language with a good amount of inspiration from C++, so the choice was only fitting.

Scott had given an almost hour-long technical keynote about quirks, inconsistencies, and instances of unintuitive behavior in C++. However, virtually all of the technical content turned out to only serve as an exceptionally elaborate buildup to a dramatic punchline lasting around 8 minutes.

At 42:10, he started to explain how the preceding 42 minutes of the presentation were meant as a warning to the D community. I embed the video below, starting at the relevant time; however, I also transcribed Scott's words for your convenience.

Perhaps more importantly, it is my perception that there is no real interest on the part of the C++ user community or the standardization committee in dealing with these kinds of issues. I don't think they think it's a problem. And I think that if you talked to every individual member of the standardization committee and you said, "Are you concerned about inconsistency, and arbitrary decisions, and simplicity, and ease of explanation?", they would all say, "Yes, those are very important." And they would not be lying. Every member of the committee would say "Yes, those are really important to me".

But, in my opinion, if you look at the results of their work, the resulting standardization document, the decisions they ultimately made as a committee, they do not reflect that concern. There is epicycle on epicycle on epicycle inside this language. And there is a lot of really well-meaning people, they do have it as a priority. The problem is, it's priority number 22, over some other ones.

D is a younger language, it is a younger library, it's got a smaller user community, it's got a much smaller legacy codebase. There is still time, if it hasn't already been done (with any luck this isn't even an issue in the D community), but there's still time to embrace a holistic "easy to explain is good" philosophy.

When I talk about "holistic", I want to contrast that with "piecemeal". In C++, for almost any rule in C++, if somebody were to say to me "Why does that exist?!", I can probably tell you. And I can probably give you a rational explanation for why it's there. Everything is there for a reason. (Except the auto type deduction rule.) Everything is there for a reason.

But a holistic approach says "each language rule is easy to justify in isolation as well as in the context of the other rules." That's what C++ does not have. What C++ has instead is a whole series of justifications that they trot out on demand. So here are some of the more popular things you'll hear to justify things in C++. [10 reasons appear on the slide] All legitimate-sounding things. But what I find interesting is: [highlights 2 pairs of contradicting reasons]

    • Trust the user
    • Prevent likely user errors
    • Don't constrain compilers
    • Favor users over compiler writers

You can justify anything with those two. [points at first pair] And you can justify anything with those two. [points at second pair] And so what tends to happen there in C++ standardization is they trot out whichever justification makes sense for the rule that they want to adopt. And they now have enough rules that it doesn't matter, because they can justify anything.

Then, he goes on to contrast low-level tool use, that is, a necessary burden and something that must be learnt, with high-level tool application, the actual goal that programmers desire to accomplish by means of using the tools:

So, the last quarter century of my life. [4 C++ books appear on the slide] I have done a couple of other things. Raised a dog. So, this book [points at Effective C++], 55 guidelines for C++; this one [More Effective C++] 35, this one 50 [Effective STL]; I'm working on a new book now [Effective Modern C++], it's gonna have 42 guidelines, because a friend of mine keeps nagging me to use 42 as an arbitrary number.

If you add these things, see, I've got 182 guidelines. Now 182 guidelines, that's an awful lot of stuff to remember about a programming language. That's on top of all the stuff you have to remember for the APIs that you're using that are not part of the standard library, not to mention you are trying to get some work done to do something.

Some of what I've written about has been tool application; but much too much of what I've written about has been tool use, talking about language idiosyncrasies, arbitrary decisions, things you need to be able to remember. So, the message that I bring to the D community based on my experience with C++ is that the last thing D needs is…

…somebody like me.

I think you get it already. There's a recurring theme in the history of programming languages of arbitrary decisions and a rush to adopt whichever features individual members of the community want. (Design-by-committee languages, that is. Those governed by some sort of a "benevolent dictator for life" don't tend to suffer nearly as much from this problem.)

Rust aficionados often praise the community for the RFC-guided open design process. My experience is that in fact it results in the same sort of tension and the same kinds of issues as a highly-bureaucratic committee would yield. However, in the case of an open-design language, the problem stems not from the amount of bureaucracy, but from the large number of people and the high rate of new ideas involved.

I was once called an antagonist on IRLO because I seem to oppose the majority of new proposals that come in. That is not l'art pour l'art "antagonism" on my part, though; I'm very much not enjoying doing that, and I would see no point in telling people no just for the sake of contradiction. There is a good reason for it. Many proposals, with a few major exceptions, tend to miss one or more imporant "holistic" issues, focusing on low-level details and one-off use cases instead. They might even be perfectly justified in their own bounded context — they just don't fit well in the philosophy of Rust as a whole.

One of the cornerstones of the Rust community is valuing diversity. A noble goal — however, during the last year or so, I increasingly felt that this goal is being used as an excuse to try and cater everyone. Rust is general-purpose, but it's still fundamentally a systems programming language at heart. Unfortunately, some Rustaceans interpret "general-purpose" as "must be perfect for anyone and everyone." I don't think that is a good or even viable approach to language design. Stuffing the language with more and more features just to ensure that more people will like it is marketing or even compulsion for conformity, and not a professional way of solving a technical problem.

A perfect example of this phenomenon in action is Ok-wrapping and throws functions. Many people wanted or still want it, because it makes error handling ostensibly easier to use. However, after a short period of getting used to, the (minor) pain associated with having to write an explicit Ok or return Err(...) quickly fades away. Another reason why it sometimes comes up is "because other languages do exception handling this way". But is there a point in creating a new programming language at all, if all we do is mimic existing languages? Exception-based error handling has long been a pain point in traditional systems and application programming languages. Rust basically solved it by combining real algebraic sum types from functional languages with traits, macros, and later, the ? operator, in a previously unseen way. Sure, it was inspired by other languages, but it improved matters by changing the state-of-the-art. Undoing these improvements for mere reasons of familiarity with, say, Java, would be a huge step back.

Not only that, but there are already plenty of long-time lovers of Rust. Many of them (myself included) switched to Rust because of the way it worked at the time. It would seem strange, and to be honest, quite unfair to me, to start arbitrarily changing its fundamental ideas, idioms, and the overall direction it's going, because of what new users might like or be attracted by. Granted, those who just found out about Rust may find some of its aspects strange, unconventional, or radical. They might even simply dislike its style. And that is perfectly fine. However, these users still have the choice whether or not to invest heavily in Rust. They can just not use it, after all, without consequences, if they don't find it suitable for their taste. But those who have already written thousands or even hundreds of thousands of lines of Rust, potentially at work, would be heavily affected if the language suddenly transformed to something completely new under their feet, requiring programmers to re-learn new idioms and styles, and to migrate all the code, even if gradually.

On a related note, I think the goal of beginner-friendliness has also been somewhat derailed. To cite a C++ expert again: Bjarne Stroustrup once said that a good programming language needs to be not only beginner-friendly or expert-friendly; it must be both. (This was in a presentation of his that I attended; I saw him reiterating the idea online.) These days I see many ideas circulating on IRLO and in the RFCs repo, usually revolving around features used by almost everyone, such as traits, mutability, error handling, or references, and how they are "hard" and consequently how we need more "beginner-friendly features" in the language.

In my opinion this attitude is solving the wrong problem. Yes, it is indeed possible to temporarily save newcomers from some pain by bundling together some parts of certain existing language features as a special-case, giving it a new, shiny name, and suggesting that people use it. However, do this for long enough and it will clutter the language, to the point where these very features themselves will become the high barrier to entry, just because of the sheer amount of choices one can (and needs to) make from the beginning. To reach for a metaphor, too much syntactic sugar makes for syntactic diabetes. (The resulting non-orthogonality is "just" a cosmetic issue, but it is still there, too.)

So, what are my suggestions for dealing with the problems that do exist?
Here they go:

Closing thoughts

It seems that the issue I brought up is a concern for other users of Rust as well. There have been several different #Rust2019 articles in this spirit:

I hope our words will be listened to, and I wish Rust will continue to thrive as the best systems programming language out there!