Writing reliable, efficient code with Rust
Recently, I’ve begun tackling a few projects in Rust, an open-source programming language designed to maximize software reliability and efficiency. Rust is used by a lot of big names — including Mozilla, Cloudflare, Dropbox, AWS, Google, MS, and Yelp — and was the #1 “most loved language” in StackOverflow’s 2018 Developer Survey.
In this post, I’ll share a bit of my experience using Rust; I’ll also talk through the language’s benefits and recommended applications.
Benefits of Rust
Overall, Rust is designed to guide you naturally towards reliable code that is efficient in terms of speed and memory usage. The language’s creators claim (and I generally agree) that it stands out in three major areas:
Performance: Rust is very fast and memory-efficient — it can power performance-critical services, run on embedded devices, and easily integrate with other languages.
Reliability: Rust’s rich type system and ownership model (more on that below) guarantee memory-safety and thread-safety, and enable you to eliminate many classes of bugs at compile time.
Productivity: Rust has great documentation, a user-friendly compiler, an integrated package manager, and other nice extras designed to help you write code efficiently.
What makes Rust unique?
To achieve the performance and reliability benefits mentioned above, Rust’s creators made several specific design choices:
Use of the “Resource Acquisition Is Initialization” (RAII) paradigm. Under this paradigm, whenever an object goes out of scope, it’s destroyed immediately. For example, if I have an object “foo”, and I make the following declaration:
the memory used to store “foo” is released immediately, just as memory is immediately allocated to “bar”. The key here is that, unlike many other languages (e.g., Java), Rust isn’t running a periodic garbage collector. Instead, it’s allocating and releasing memory dynamically as the code executes — which is much more memory-efficient.
One-to-one ownership. In Rust, because variables are in charge of freeing their own resources, resources can only have one owner; for example, consider the following code, which passes a variable into a function and then tries to access it again in the same scope:
This results in a compiler error, because ownership of “foo” was passed into another scope, but it can’t be in two places at once.
It is possible, however, to use variables in more than one place if we pass them by reference. For example:
will compile, because the “&” specifies that we are passing a reference to “foo”, rather than passing “foo” itself — so ownership of “foo” stays in the same scope.
Explicit mutability. A mutable variable is one that can be changed; an immutable variable is fixed. Rust declares mutable and immutable variables differently. Consider the following examples:
In the first example, the assignment statement foo = “silly”; would result in a compiler error. In the second example, however, the assignment is allowed.
I’m only scratching the surface of the concepts built into Rust, but I can say that the rigidity of Rust’s rules — including those around ownership and mutability — force you, as the developer, to think very carefully about your intention for the flow and handling of data in a program. If you aren’t carrying out a specific and consistent intention, you’ll probably find yourself writing code that doesn’t compile.
Working with Rust
My first few attempts at coding in Rust felt like one drawn-out fight with the compiler. As I adjusted to the more stringent rules, however, I found that my development practices were actually improving. I was more inclined to write tests first, and to build my programs incrementally. And the upshot of the exacting compiler was that when my code did compile, I felt confident that I had everything right.
I was also sold on Rust’s performance benefits. To compare a Rust implementation against other options, I decided to re-implement Node.js code for a past project that needed to parse a saved binary data file into data structures for subsequent processing. At its fastest, the Node version parsed a 220mb file in about 6.5 seconds; my first attempt at a Rust version ran in 2.5 seconds, more than a 60% improvement. The parsing itself is not parallelizable, but I see an opportunity here to write a multi-threaded stream parser in Rust that would likely greatly outperform a Node version handling multiple concurrent inbound streams.
Should you try Rust?
If you’re in the business of building npm modules, command line scripts, microservices, webassembly or anything else that requires reliable, efficient code, I would definitely recommend giving Rust a try. It’s a thoughtful, innovative language that will nudge you to improve your development practices, and as it’s being rapidly adopted by both major companies and independent developers, it’s also supported by excellent documentation and tooling. If you do try it out, good luck, and have fun!