“Safe Systems Programming in Rust”
Communications of the ACM, April 2021, Vol. 64 No. 4, Pages 144-152
By Ralf Jung, Jacques-Henri Jourdan, Robbert Krebbers, Derek Dreyer
“We hope to inspire other members of the computer science research community to start paying closer attention to Rust and to help contribute to the development of this groundbreaking language.”
There is a longstanding tension in programming language design between two seemingly irreconcilable desiderata.
- Safety. We want strong type systems that rule out large classes of bugs statically. We want automatic memory management. We want data encapsulation, so that we can enforce invariants on the private representations of objects and be sure that they will not be broken by untrusted code.
- Control. At least for “systems programming” applications like Web browsers, operating systems, or game engines, where performance or resource constraints are a primary concern, we want to determine the byte-level representation of data. We want to optimize the time and space usage of our programs using low-level programming techniques. We want access to the “bare metal” when we need it.
Sadly, as conventional wisdom goes, we cannot have everything we want. Languages such as Java give us strong safety, but it comes at the expense of control. As a result, for many systems programming applications, the only realistic option is to use a language like C or C++ that provides fine-grained control over resource management. However, this control comes at a steep cost. For example, Microsoft recently reported that 70% of the security vulnerabilities they fix are due to memory safety violations, precisely the type of bugs that strong type systems were designed to rule out. Likewise, Mozilla reports that the vast majority of critical bugs they find in Firefox are memory related. If only there were a way to somehow get the best of both worlds: a safe systems programming language with control…
Enter Rust. Sponsored by Mozilla and developed actively over the past decade by a large and diverse community of contributors, Rust supports many common low-level programming idioms and APIs derived from modern C++. However, unlike C++, Rust enforces the safe usage of these APIs with a strong static type system.
In particular, like Java, Rust protects programmers from memory safety violations (for example, “use-after-free” bugs). But Rust goes further by defending programmers against other, more insidious anomalies that no other mainstream language can prevent. For example, consider data races: unsynchronized accesses to shared memory (at least one of which is a write). Even though data races effectively constitute undefined (or weakly defined) behavior for concurrent code, most “safe” languages (such as Java and Go) permit them, and they are a reliable source of concurrency bugs.35 In contrast, Rust’s type system rules out data races at compile time.
Rust has been steadily gaining in popularity, to the point that it is now being used internally by many major industrial software vendors (such as Dropbox, Facebook, Amazon, and Cloudflare) and has topped Stack Overflow’s list of “most loved” programming languages for the past five years. Microsoft’s Security Response Center Team recently announced that it is actively exploring an investment in the use of Rust at Microsoft to stem the tide of security vulnerabilities in system software.
The design of Rust draws deeply from the wellspring of academic research on safe systems programming. In particular, the most distinctive feature of Rust’s design—in relation to other mainstream languages—is its adoption of an ownership type system (which in the academic literature is often referred to as an affine or substructural type system). Ownership type systems help the programmer enforce safe patterns of lower-level programming by placing restrictions on which aliases (references) to an object may be used to mutate it at any given point in the program’s execution.
However, Rust goes beyond the ownership type systems of prior work in at least two novel and exciting ways:
Rust employs the mechanisms of borrowing and lifetimes, which make it much easier to express common C++-style idioms and ensure they are used safely.
Rust also provides a rich set of APIs—for example, for concurrency abstractions, efficient data structures, and memory management—which fundamentally extend the power of the language by supporting more flexible combinations of aliasing and mutation than Rust’s core type system allows. Correspondingly, these APIs cannot be implemented within the safe fragment of Rust: rather, they internally make use of potentially unsafe C-style features of the language, but in a safely encapsulated way that is claimed not to disturb Rust’s language-level safety guarantees.
These aspects of Rust’s design are not only essential to its success—they also pose fundamental research questions about its semantics and safety guarantees that the programming languages community is just beginning to explore.
In this article, we begin by giving the reader a bird’s-eye view of the Rust programming language, with an emphasis on some of the essential features of Rust that set it apart from its contemporaries. Second, we describe the initial progress made in the RustBelt project, an ongoing project funded by the European Research Council (ERC), whose goal is to provide the first formal (and machine-checked) foundations for the safety claims of Rust. In so doing, we hope to inspire other members of the computer science research community to start paying closer attention to Rust and to help contribute to the development of this groundbreaking language.
About the Authors:
Ralf Jung is a postdoc at the Max Planck Institute for Software Systems, Germany.
Jacques-Henri Jourdan is a researcher at the Université Paris-Saclay, CNRS, ENS Paris-Saclay, Laboratoire des méthodes formelles, France.
Robbert Krebbers is a (tenured) assistant professor at Radboud University Nijmegen, The Netherlands.
Derek Dreyer is a professor at the Max Planck Institute for Software Systems, Germany.