Rust: the emergent complexity of a simple language

As promised last time, I would like to reflect a bit about Rust after using it for the last year. This post is not meant to be an introduction to Rust, rather I would like to give you subjective view of how it feels to use Rust1.

"A language empowering everyone to build reliable and efficient software."

The Rust slogan -- rust-lang.org

But first things, first. What is Rust? Rust is programming language that was conceived for low-level programming with a focus on correctness and concurrency. While it was very much designed to be used in systems programming, it has turned out to be useful in lots of other contexts, too. This fact is also reflected in the new slogan. Whereas the old slogan still contained explicit references to system's programming, the new slogan mirrors a more inclusive view. The reality is that with its focus on "performance, reliability, and productivity"2 Rust can be used in all kinds of situations. People are using Rust everywhere. The list includes operating systems, game dev, web dev (both front & backend), and embedded. Rust is also more and more used to build extension modules for high level languages, such as Python3.

"In a system's programming language you do not have the luxury of pretending that the machine is not what the machine actually is."

Steve Klabnik -- The history of Rust

One of the biggest focus points of Rust is performance. Rust aims to be as close to the metal as possible, while stile providing high level abstractions. More often than not this is accomplished as Zero Cost Abstractions, high level features that come at no runtime cost4. With its lack of runtime, Rust is also very portable. Rust programs can be run on their own just as easily, as they can be embedded into Python or into websites. Having a single language that is able to span backend to frontend, from embedded devices to server farms is dizzying.

Personally speaking: after a long time of writing Python, I'm just excited to write programs without constantly wondering about performance. I can finally freely use for loops and do not have to wonder about external libraries to speed them up. Further, I really enjoy the fact that Rust has no runtime and no garbage collector. For example, I still use Python as an interactive wrapper, but also use the same code from Kotlin without issue.

"Just because Rust allows you to write super cool non-allocating zero-copy algorithms safely, doesn’t mean every algorithm you write should be super cool, zero-copy and non-allocating."

trentj -- rust-users, via this-week-in-rust

While manual memory management and performance oriented programming comes at a higher mental overhead, it makes the resulting system often much simpler to use. And in times of serverless architectures also more cost efficient. You can write highly optimized, zero-copy code in Rust. And because it's possible, sometimes I feel, like I have to do so. But more often than not the additional performance loss of making more copies or using smart pointers is negligible, while making the programs much easier to write. Many of the difficulties of Rust vanish with a slightly more liberal use clone, dyn, or Rc.

"Rust has lost more features than many languages had in the first place."

Steve Klabnik -- The history of Rust

Rust is often said to be hard to learn or to be a complex language. My feeling is that Rust itself is rather a simple language. If you think about C and C++ sitting on a continuum it is much closer to C than to C++. It achieves this feat with a combination of consistency and minimalism. A relatively small number of features is consistently used in many different places, for example the same patterns are used in function definitions, let statements, or match statements. Of course, Rust is a statically typed language with strict safety guarantees and without garbage collection. The task it sets out to solve brings with itself an inherent complexity. To allow ergonomic use, Rust leverages its standard library, which contains many features that have originally been language features. While this shift from language to library, made the language simpler, I feel it made it at times more complex to use.

There are many small building blocks that need to be combined, in sometimes very specific ways. A prototypical example is the combination of Arc and Mutex (or Rc and RefCell in a single threaded context). Wrapping an object as Arc<Mutex<T>> allows the object to be owned and mutated in a shared fashion. When I started with Rust, it took me much longer than I like to admit, to understand this specific pattern. And, even now I wonder from time to time whether to place the whole object in a Mutex or go for a more fine-grained approach. Similar issues arise with the language itself. Some functionality requires very specific combinations of traits and generics to look like idiomatic Rust code.

Rust gets away with the complexity issue in part thanks to its great tooling and superb documentation. With Rust analyzer writing Rust is a joy, but also cargo removes a lot of the hassle. In general, the tooling around Rust is consistently pleasant to use. And since these tools are standardized, transferring knowledge from one project to the next is typically very simple. Also the documentation makes it easy to get started and features great learning resources, such as the Rust book or Rust by example. But my personal highlight is the standard library documentation, which is written extremely well and finds a nice balance between theoretical and practical explanations supported by plenty of examples. At this point I would also give a shout out to Ken Powers for his redirect std.rs. Probably my most typed URL.

In addition, Rust gives you the tools to built good abstractions on top of its foundations and to ship them as libraries. A small caveat upfront: I fear that discovering these libraries in itself can be a problem. Just as an example: I always found error handling very mystifying. Nowadays, I simple use anyhow for my quick experiments and error handling has become a non issue. As for the standard library, I feel sometimes an "easy" mode is missing. As an example, path handling is very general and probably very correct, but also at times hard to use5. Offering more high level abstractions that focus on usability, while maybe sacrificing performance or portability, could make Rust much easier to start with.

As said last time, Rust has become my primary language and I am still incredibly excited for it. The reasons are mainly Rust's performance and portability, but also the language design, including how stripped down and simple the language itself feels to me. I also simply enjoy writing code in Rust, a feeling I find hard to pin down. Not everything is perfect and Rust can be pretty arcane at times. The lack of interactivity is also big issue for me. I do think most of the issues can be addressed with better tooling and documentation. For some aspects, new language features may be required. In my view, this step should be the last resort, though. I feel it's easier to simplify a system that has become complex, than a system that is complex in itself.

As always feel free to reach out to me on twitter @c_prohm with feedback or comments.


1: If I you are interested in learning rust, I can highly recommend the official Rust learning resources, such as the Rust book or Rust by example.

2: Lifted from rust-lang.org's why Rust section.

3: The increased use of Rust seems also unfortunately to increase the vitriol in recent discussions of the language.

4: One canonical example are optional references which compile down to pointers with missing values encoded as NULLs.

5: As of now, I did not have time to checkout camino. I could image that it solves some of the issues I ran into.