Needless of a lengthy introduction, Rust is a systems-level programming language that puts "performance", "reliability", and "productivity" at the forefront of its core values. Namely, its most notable language features include memory safety guarantees with the ownership model, a standardized package manager with Cargo, "zero-cost abstractions" with the standard library, powerful multi-threading primitives and idioms, and low-level interoperability with C/C++.
It comes to no surprise, then, that Rust has steadily been on the rise in terms of adoption and popularity. In fact, for the fifth consecutive year, Rust has taken the top spot as the "Most Loved Programming Language" according to the 2020 Stack Overflow Survey.
But is all the hype justified? In this article, I reflect on my experience with Rust in the past year. This includes the golden moments as well as the pain points. Without further ado, let us begin with the golden moments.
Perhaps the most significant barrier to entry for any programming language is the development environment. A convoluted toolchain is sure to deter anyone new to the language. I learned this the hard way a few years ago when I had to mess with the system
PATH and other environment variables just to compile my first Java program.
Much to my relief, Rust does not suffer from this issue. Thanks to the rustup toolchain utility, the installation process could not be any simpler for a systems-level programming language. Although the Rust compiler depends on platform-specific build systems, this extra step should not be too much of a hassle given that the installation process for these external dependencies are also relatively straightforward. The Visual Studio Build Tools for Windows, for example, provides an automated installer.
When it comes to project development, the Cargo package manager makes dependency management and build system configuration an absolute breeze. Noting that Rust is a systems-level programming language, it surprised me how simple it was to get a project up and running without any boilerplate and prior configuration. Quite literally, it just works!
As for dependency management, I felt at home when I pulled in external packages. The workflow was strikingly similar to that in Node.js: step 1, list down the package name in the project manifest; step 2, indicate the version number (with respect to semantic versioning); step 3,
use the dependency; step 4, compile the project; step 5, profit. Indeed, Rust upholds its promise of "productivity" with this simple workflow.
For comparison, this was totally unlike my initial experience with C++. Before I was able to pull in dependencies, I had to dive deep into the rabbit hole of precompiled headers, static libraries, dynamic libraries, linkers, and Git submodules. Needless to say, it was not the funnest experience, especially when I just wanted to hack around the language.
But to be fair to C++, the language does not come with a standardized package manager out of the box. Personally speaking, this is exactly what makes Rust all the more attractive.
Perhaps the most praiseworthy aspect of the Rust ecosystem is its commitment to clear and accurate documentation. Nowhere is this more evident than in the official language documentation. Indeed, the Rust team leads by example in this regard.
In order to avoid having a bloated, monolithic website for all things about Rust, the Rust team separated the language documentation across multiple e-books that cover specific areas of interest. To name a few in particular:
|Title||Area of Interest||Description|
|The Rust Book||First Principles||Serves as the introduction to Rust.|
|Rust by Example||Applied First Principles||Illustrates the practical usage of the language's features.|
|The Standard Library||Standard Library||Serves as the official reference to the standard library.|
|The Cargo Book||Cargo||Serves as the official reference for all things about the Cargo package manager.|
|The Rust Reference||Full Language Documentation||Serves as the more "complete" version of the Rust Book.|
|The Rustonomicon||Unsafe Rust||Serves as the official guide to the "dark arts" of unsafe Rust.|
Indeed, all of these e-books are a treasure trove of information. They strike the perfect balance between approachability, reasonability, and extensiveness. When a topic deems it necessary, the writing style shifts to a drier, more technical tone. Otherwise, the documentation tends to be a playfully engaging reading experience with colorful examples.
Furthermore, documentation is itself a core language feature! Using the
doc subcommand, Cargo can extract all doc-comments in the codebase. It then uses this information to automatically generate static web pages for documentation. In fact, for every package published to crates.io (Rust's package registry), docs.rs hosts the corresponding static web pages generated by
This extremely valuable feature allowed me to confidently explore and navigate the interfaces of the libraries I use for my projects. To me, the best part about it is the fact that
cargo doc generates the same web page structure for all packages. Eventually, I found myself comfortably accustomed to the familiar documentation format—regardless of the current library. At one point, I even spent almost half a day just scrolling through the documentation for the various methods of each standard collection type in the Standard Library.
Once again, this attests to Rust's promise of "productivity". Although the documentation format may seem boring and repetitive to some, it at least communicates information about interfaces in a standardized manner. Over time, every Rustacean learns how to treat docs.rs as their second home.
It is no secret that the Rust ownership model practically makes certain design patterns and data structures difficult to deal with. One of the most notorious examples is the humble linked list. If we were to quickly run a Google search for "rust linked list", we would find a plethora of resources describing the fundamental nuances of the ownership model.
To some, this is an incredible barrier of the language. Personally, I would argue otherwise: the design patterns and idioms of Rust help us rewire our brains to design better software. This is not to say that data structures like linked lists are inherently evil, but there are valid reasons why it is difficult to deal with in the first place.1
With that said, over the past year, I have come to accept the reality that if I end up with an architecture that frequently conflicts with the Rust compiler, there is most likely something worth reworking in my current implementation. This is apparently a common mindset among Rustaceans: don't fight the compiler.
When I finally conceived of a better architecture for the project2, I realized the truth and wisdom behind the Rustacean Mindset. If I had pushed through with my original architecture, I would have surely ended up with an unmaintainable mess of a project with hundreds of lines of "spaghetti code", all coupled and tangled up in more ways than one.
Instead, I allowed the idioms of Rust to rewire my brain. In the end, I arrived at a better architecture for my project. Thinking like a Rustacean opened my eyes to see better software design patterns that are not necessarily exclusive to Rust itself. Indeed, don't fight the compiler.
I won't sugarcoat anything here: Rust is one formidable beast of a programming language. This invites two possible interpretations. The first of which is that Rust is a powerful language. As for the other, Rust is a painfully difficult language to deal with. Sure enough, both of these are true.
The absolute power of Rust comes from the juxtaposition of its relatively high-level syntax and its low-level capabilities. Compared to its peers, Rust is truly a breath of fresh air. Many particularly cite the verbosity of C++ as a case for Rust's superior expressiveness. I mean, have you seen template metaprogramming code in C++?
Although Rust is indeed more concise and expressive than its peers, it does not mean it is free from shortcomings. The low-level nature of its design makes some design decisions and complexities a necessary trade-off. A prime example is the ownership model and the concept of object lifetimes.
Without going too deep into the weeds, since the beginning, the Rust team envisioned a truly high-level language with performance comparable to that of C/C++. This meant that it was against Rust's core philosophy to impose a language runtime with a garbage collector. Thus, each object in Rust was responsible over their own memory. And hence, the ownership model was born. To put it simply, the ownership model is basically a memory management contract that Rust upholds in order to avoid memory leaks and null pointers.3
This feature alone justifies the low-level power of Rust, yet at the same time, it is also the #1 source of confusion for many Rustaceans according to the recent 2020 Rust Survey. The results show that as many as 61.4% of the respondents agreed that object lifetimes were either "tricky" or "very difficult" to learn.
Indeed, from my experience, this was the trickiest concept to grasp, even with prior knowledge of C++ patterns. Though, to be fair to Rust, this is not entirely the language's fault. Any language that deals with low-level operations inevitably has to introduce complexity into its abstractions.
Over the years, crates.io has accumulated over 50,000+ packages. Among the most beloved are
rand (for random number generation),
quote (for macro-related utilities),
rocket (for web server frameworks),
bevy (for game development),
clap (for parsing command-line arguments), and
serde (for data serialization and deserialization).
However, despite the richness of the ecosystem, I am still uncertain whether I can definitively say that it is fully "mature". Indeed, many of the packages are considered to be "production-ready". But, if we were to explore the list of the most downloaded Rust packages (as of writing), we would observe that many of them have not been stabilized to (at least)
With respect to semantic versioning, these packages are not yet considered to be "feature-complete" by their authors. One can argue, then, that a significant portion of the entire Rust package registry is not yet "feature-complete". Hence, I assert that Rust enjoys a rich ecosystem of maturing packages.
It is worth noting here that I am careful in choosing the word "maturing" over "mature". In most cases, "production-ready" is sufficient, but if Rust intends to accumulate massive market adoption, achieving maturity and stability is a key milestone. This is exactly what makes C/C++ an "immortal language" of sorts. From media encoders to cryptography tools, the world quite literally runs on C/C++.
To the community's credit, the 2020 Rust Survey paints a more optimistic picture in regard to library support. According to 65.9% of the respondents, there have been "at least some improvement" in the library ecosystem overall. However, GUI programming appears to be the exception, noting that an overwhelming 73.1% of the respondents opined that the ecosystem lacked enough support in this particular application domain.
With all things considered, I would say that Rust truly lives up to the hype. Just like for any other language, if one were to invest enough time and patience, they would find that Rust is not so bad. After getting over the initial learning hump, everything else becomes much easier.
The high-level syntax and the zero-cost abstractions are a dynamic duo that makes systems-level programming an absolute breeze. In times where it is otherwise, the excellent language documentation is always there to lift us up. Meanwhile, the Cargo package manager makes dependency management an empowering experience. One only needs to pull in a package to extend low-level functionality.
On another note, adopting the Rustacean Mindset rewires the brain to think more carefully about certain software design patterns. The ownership model invites us to question the validity of our data structures. Although fighting the compiler will be an awfully frustrating experience, let us never forget that this is always done in our best interests. When in doubt, we need to step back, breathe, and think. A well-thought solution is always better than "spaghetti code".
Ironically enough, although a significant portion of the Rust package ecosystem is not yet "feature-complete", this gives me all the more reason to place my bets in the ecosystem. Considering the current capabilities of Rust despite the "limited" ecosystem, the future seems bright for the language.
Perhaps a decade from now, we will finally be able to crown Rust as an "immortal language" alongside the likes of C/C++. Personally, I am looking forward to that day! But until then, I must say that the Rust hype is indeed justified.
Before I conclude this article, I would like to cordially thank the Rust team for their excellent work on both the language and the community. Without them, we would have none of the innovations we enjoy today in systems-level programming.
To cite one reason, a linked list is considered to be a "last-resort" data structure in Rust primarily due to memory fragmentation and frequent heap allocations. In most cases, arrays and vectors are sufficient. ↩
Funny story: the "Eureka moment" occurred to me just before I was about to sleep at night. Yes, I was frustratingly restless after that point... 🤦♂️ ↩
This is done through the use of destructors that run when objects go "out of scope". In C++, this is analogous to the Resource Acquisition is Initialization (RAII) principle. ↩