Fast, safe, easy to write—pick any two. That’s been the state of software development for a good long time now. Languages that emphasize convenience and safety tend to be slow (like Python). Languages that emphasize performance tend to be difficult to work with and easy to blow off your feet with (like C and C++).
Can all three of those attributes be delivered in a single language? More important, can you get the world to work with it? The Rust language, originally created by Graydon Hoare and currently sponsored by Mozilla Research, is an attempt to do just those things. (The Google Go language has similar ambitions, but Rust aims to make as few concessions to performance as possible.)
What makes Rust a better development language
Rust started as a Mozilla research project partly meant to reimplement key components of the Firefox browser. A few key reasons drove that decision: Firefox deserved to make better use of modern, multicore processors; and the sheer ubiquity of web browsers means they need to be safe to use.
But those benefits are needed by all software, not just browsers, which is why Rust evolved into a language project from a browser project. Rust accomplishes its safety, speed, and ease of use through the following characteristics:
Rust satisfies the need for speed. Rust code compiles to native machine code across multiple platforms. Binaries are self-contained, with no runtime, and the generated code is meant to perform as well as comparable code written in C or C++.
Rust won’t compile programs that attempt unsafe memory usage. Most memory errors are discovered when a program is running. Rust’s syntax and language metaphors ensure that common memory-related problems in other languages—null or dangling pointers, data races, and so on—never make it into production. The compiler flags those issues and forces them to be fixed before the program ever runs.
Rust controls memory management via strict rules. Rust’s memory-management system is expressed in the language’s syntax through a metaphor called ownership. Any given value in the language can be “owned,” or held/manipulated, only by a single variable at a time.
The way ownership is transferred between objects is strictly governed by the compiler, so there are no surprises at runtime in the form of memory-allocation errors. The ownership approach also means there is no garbage-collected memory management, as in languages like Go or C#. (That also gives Rust another performance boost.) Every bit of memory in a Rust program is tracked and released automatically through the ownership metaphor.
Rust lets you live dangerously if you need to, to a point. Rust’s safeties can be partly suspended where you need to manipulate memory directly, such as dereferencing a raw pointer à la C/C++. The key word is partly, because Rust’s memory safety operations can never be completely disabled. Even then, you almost never have to take off the seatbelts for common use cases, so the end result is software that’s safer by default.
Rust is designed to be easy to use. None of Rust’s safety and integrity features add up to much if they aren’t used. That’s why Rust’s developers and community have tried to make the language as useful and welcoming as possible to newcomers.
Everything needed to produce Rust binaries comes in the same package. External compilers, like GCC, are needed only if you are compiling other components outside the Rust ecosystem (such as a C library that you’re compiling from source). Microsoft Windows users are not second-class citizens, either; the Rust tool chain is as capable there as it is on Linux and MacOS.
On top of all that, Rust provides several other standard-issue items you’d expect or want:
- Support for multiple architectures and platforms. Rust works on all three major platforms: Linux, Windows, and MacOS. Others are supported beyond those three. If you want to cross-compile, or produce binaries for a different architecture or platform than the one you’re currently running, a little more work is involved, but one of Rust’s general missions is to minimize the amount of heavy lifting needed for such work. Also, although Rust works on the majority of current platforms, it’s not its creators’ goal to have Rust compile absolutely everywhere—just on whatever platforms are popular, and wherever they don’t have to make unnecessary compromises to do so.
- Powerful language features. Few developers want to start work in a new language if they find it has fewer, or weaker, features than the ones they’re used to. Rust’s native language features compare favorably to what languages like C++ have: Macros, generics, pattern matching, and composition (via “traits”) are all first-class citizens in Rust.
- A useful standard library. One part of Rust’s larger mission is to encourage C and C++ developers to use Rust instead of those languages whenever possible. But C and C++ users expect to have a decent standard library—they want to be able to use containers, collections, and iterators, perform string manipulations, manage processes and threading, perform network and file I/O, and so on. Rust does all that, and more, in its standard library. Because Rust is designed to be cross-platform, its standard library can contain only things that can be reliably ported across platforms. Platform-specific functions like Linux’s epoll have to be supported via functions in third-party libraries such as libc, mio, or tokio.
- Third-party libraries, or “crates.” One measure of a language’s utility is how much can be done with it thanks to third parties. Cargo, the official repository for Rust libraries (called “crates”) lists some ten thousand crates. A healthy number of them are API bindings to common libraries or frameworks, so Rust can be used as a viable language option with those frameworks. However, the Rust community does not yet supply detailed curation or ranking of crates based on their overall quality and utility, so you can’t easily tell what works well.
- IDE tools. Again, few developers want to embrace a language with little or no support in the IDE of their choice. That’s why Rust recently introduced the Rust Language Server, which provide live feedback from the Rust compiler into an IDE such as Microsoft Visual Studio Code.
Where Rust falls short
Even with all its attractive, powerful, and useful capabilities, Rust does have things that trip up both new “rustaceans” (as Rust fans call each other) and old hands alike:
The newness of the Rust language. Rust is still a young language, having delivered its 1.0 version only in 2015. So, while much of the core language’s syntax and functionality has been hammered down, a great many other things around it are still fluid.
Asynchronous operations, for example, still aren’t represented well in the language’s syntax; work is under way to implement async operations using traits. This is a markedly different way to do async than in other languages (for example, Python’s async/await), and debate is still under way as to whether it would be better to add explicit syntax to Rust for async operations.
Rust’s learning curve. If any one thing about Rust is most problematic, it’s how tough it can be to wrap your head around Rust’s metaphors. Ownership, borrowing, and Rust’s other memory management conceits trip everyone up the first time. Many newbie Rust programmers have a common rite of passage, “fighting the borrow checker,” where they discover firsthand how meticulous the compiler is about keeping mutable and immutable things separate.
Some of the learning curve also comes from how these conceptual changes make for more verbose code, compared to other languages. For example, string concatenation in Rust isn’t always as straightforward as string1+string2. One object might be mutable and the other immutable. Rust is inclined to insist that the programmer spell out how to handle such things, rather than let the compiler guess.
Another example: how Rust and C/C++ work together. Much of the time, Rust is used to plug into existing libraries written in C or C++; few projects in C and C++ are rewritten from scratch in Rust. (And when they are, they tend to be rewritten incrementally.)
The Rust team is conscious of many of these issues, and it has plans on the 2017 Rust roadmap to improve them. For example, to make Rust easier to work with C and C++, the Rust team is investigating whether to expand projects like bindgen, which automatically generates Rust bindings to C code.
Still, Rust succeeds in its goal to provide a safe, concurrent, and practical systems language, in ways other languages don’t and to do it in ways that complement how developers already work.