Diving Into Rust for the First Time
Rust is a new programming language which focuses on performance, parallelization, and memory safety. By building a language from scratch and incorporating elements from modern programming language design, the creators of Rust avoid a lot of “baggage” (backward-compatibility requirements) that traditional languages have to deal with. Instead, Rust is able to fuse the expressive syntax and flexibility of high-level languages with the unprecedented control and performance of a low-level language.
Choosing a programming language usually involves tradeoffs. While most modern high-level languages provide tools for safe concurrency and memory safety, they do this with added overhead (e.g. by using a GC), and tend to lack performance and fine-grained control.
To deal with these limitations, one has to resort to low-level languages. Without the safety nets of most high-level languages this can be fragile and error-prone. One suddenly has to deal with manual memory management, resource allocation, dangling pointers, etc. Creating software that can leverage the growing number of cores present in today’s devices is difficult — making sure said code works correctly is even harder.
So how does Rust incorporate the best of both worlds in one language? That’s what we’ll show you in this article. Rust 1.0.0 stable has just been released. The language already has a vibrant community, a growing ecosystem of crates (libraries) in its package manager, and developers harnessing its capabilities in a variety of projects. Even if you’ve never touched a lower-level language, it’s a perfect time for you to dive in!
Use cases for Rust today
So for systems hackers, Rust seems like a great choice, but how about those who are not familiar with low-level languages? Maybe the last time you heard the words “C” and “stack/heap allocation” was 10 years ago in CompSci 101 (or maybe never). Rust provides the performance typically seen only in low-level systems languages — but most of the time it certainly feels like a high-level language! Here are a few examples of how you can leverage Rust now in practical applications:
I want to hack on hardware/write Internet-of-Things applications
The IoT era and the expansion of the maker movement enables a real democratization of hardware projects. Whether it is the Raspberry Pi, Arduino, or one of the young titans like the BeagleBone or Tessel, you can choose from a slew of languages to code your hardware projects, including Python or JavaScript.
There are times, however, when the performance these languages offer is simply not adequate. At other times, the microcontroller hardware you are aiming for is not suited to the runtimes these languages require: slow chips with tiny memory reserves and ultra-low-power applications still require a close-to-the-metal language. Traditionally that language has been C — but as you might have guessed, Rust is the new kid on the block.
Rust supports a wide variety of exotic platforms. While some of this is still experimental, support includes generic ARM hardware, the Texas Instruments TIVA dev board, and even the Raspberry Pi. Some of the newest IoT boards like the Tessel 2 even come with official, out-of-the-box Rust support!
I’m operating high-performance computing applications that scale to multiple cores
Studies suggest Rust is already great for HPC (high-performance computing). You don’t even have to rewrite your whole application in Rust: its flexible Foreign Function Interface (FFI) provides efficient C bindings that let you expose and call Rust code without any noticable overhead. This allows you to rewrite your app module by module, slowly transitioning towards a better developer experience that will result in performance on par with the old code or better. You also get a more maintainable codebase with fewer errors, which scales better on a high number of cores.
I simply need something fast!
Rust is great for rewriting performance-sensitive parts of your application. It interfaces well with other languages via FFI and has a tiny runtime that competes with C and C++ in most cases, even when resources are limited.
Despite the work-in-progress nature of the language, there are already business-critical, live production applications that have been making good use of Rust for some time: Yehuda Katz’s startup, Skylight uses high-performance Rust code embedded in a ruby gem for data-crunching. The 1.0.0 stable release is also an important milestone in that no breaking changes should happen from now on. That makes it safe to recommend Rust for even the most demanding and robust applications!
Watch Yehuda Katz and Tom Dale talk about the basics of Rust programming and how they used Rust with Ruby in their app.
Getting started with Rust
There are many tutorials covering Rust in general, as well as specific aspects of the language. For example, the Rust blog has great articles on various facets of developing Rust applications. There are also excellent introductory talks such as Aaron Turon’s talk at Stanford University. He explains the main concepts and motivations behind Rust nicely, and the talk serves as the perfect appetizer, starting you on your journey:
Talks and tutorials are no substitute for writing code, right? Rust’s got you covered in that regard, too! The Rust Book was conceived to help you get started — from installation to that first Hello World to providing an in-depth reference for all the core language features.
Another resource worth mentioning is Rust by Example, which guides you through Rust’s key features, from the basics to the arcane powers of traits, macros, and the FFI. Rust By Example offers invaluable live-editable, in-browser examples with all of the articles. You don’t even have to download and compile Rust, as you can try (and edit) all of these examples from the comfort of your living room couch, just as they are. There is also a Rust Playpen for the exact same purpose.
Not that downloading and installing Rust is much trouble, anyway. Head to the Rust homepage and download the appropriate binary/installer there. The downloads contain all the tools you need to get started (like rustc, the compiler, or cargo the package manager), and are available pre-built for all platforms (Windows, Linux, and OSX).
What is it then, that makes Rust so unique, so different?
The Rust Way
Arguably it’s Rust’s unique ownership model that allows it to really shine — eliminating whole classes of errors related to threading and memory management, while easing development and debugging. It is also invaluable in keeping the language runtime minimal and the compiler output lean and performant.
The ownership model
The basic principle of Rust’s ownership model is that every resource can belong to one and only one “owner.” A “resource” can be anything — from a piece of memory to an open network socket to something more abstract like a mutex lock. The owner of said resource is responsible for using, possibly lending out the resource (to other users), and finally cleaning it up.
All this happens automatically, with the help of scopes and bindings: bindings either own or borrow values, and last till the end of their scope. As bindings go out of scope, they either give their borrowed resource back, or in case they were the owner, dispose of the resource.
What’s even better is that the checks required for the ownership model to work are executed and enforced by the Rust compiler and “borrow checker” during compilation, which in turn results in various benefits.
Zero-cost abstractions
Since the correct operation of a program is asserted at compilation time, code that compiles can generally be considered safe with regard to most common types of memory and concurrency errors (such as use-after-free bugs or data races). This is possible because Rust complains at compilation time if the programmer attempts to do something that violates the principles above, which in turn helps keep the language runtime minimal.
By deferring these checks till compilation time, there’s no need to perform them during runtime (code is already “safe”), and no runtime overhead. In the case of memory allocations, there is no need for runtime garbage collection, either. This — advanced language constructs with little to no runtime overhead — is the basic idea of “zero-cost abstractions.”
Though a thorough explanation of the ownership model is outside the scope of this article, the Rust Book (linked above) and various talks and articles excel in explaining its principles. The principles of ownership and the borrow checker are essential for understanding Rust, and are key to other powerful aspects of the language, like its handling of concurrency and parallelism.
Two birds, one language construct
Shared mutable state is the root of all evil. Most languages attempt to deal with this problem through the mutable part, but Rust deals with it by solving the shared part.
— The Rust Book
Aside from memory safety, paralellism and concurrency are the second most important focus in Rust’s philosophy. It might seem surprising, but the ownership system (coupled with a few handy traits) also provides powerful tools for threading, synchronization, and concurrent data access.
When you try some of the built-in concurrency primitives and start digging deeper into the language, it may come as a surprise that those primitives are provided by the standard library, rather than being part of the language core itself. Thus, coming up with new ways to do concurrent programming is in the hands of library authors — rather than being hard-wired into Rust, restricted by the language creators’ future plans.
Performance or expressiveness? Choose both!
Thanks to its static type system, carefully selected features, and having no garbage collector, Rust compiles into performant and predictable code that’s on par with code written in traditional, low-level languages (such as C/C++).
By moving the various checks out of the runtime and getting rid of garbage collection, the resulting code mimics the performance characteristics of other low-level languages, while the language itself remains much more expressive. The (seemingly) high level constructs (strings, collections, higher order functions, etc.) mostly avoid the runtime performance hit commonly associated with them. Since the Rust compiler’s output is in the LLVM intermediate representation, the final machine code makes good use of the LLVM compiler’s cross-platform benefits and all additional (current and future) optimizations automatically.
We could go on for hours about how with Rust you never have to worry about closing sockets or freeing memory, how traits and macros give you incredible flexibility for overloading operators, or even working with Rust’s functional aspect, juggling iterators, closures and higher order functions. But we wouldn’t want to overwhelm you early on! Sooner or later you will come across these anyway — and it’s more fun to discover for yourself exactly how deep the rabbit hole is.
So now instead, let’s finally look at some code…