Rust: Building Reliable and Performant Software

Rust is quietly spreading as a language of choice for building reliable and performant applications. But what makes it different? Ellis Real, a software engineer on Google's Android Rust team, a core maintainer of Tokio (the de facto async runtime for Rust), and a Rust language team advisor, joins us to discuss the compelling reasons to consider Rust, whether you're coming from TypeScript or C++.

In this conversation, we delve into concepts like ownership, the borrow checker, and the unsafe keyword. We also explore what trips up newcomers to Rust, how the language is governed without a benevolent dictator, and the mechanics of RFCs and "editions." If you want to understand what sets Rust apart and why so many engineers attest that "once it compiles, it works," this episode is for you.

Tokio: An Asynchronous Runtime

Tokio is an asynchronous runtime for Rust, essentially serving as the standard library for asynchronous programming in the language. Similar to how JavaScript in a browser uses an event loop to manage tasks, Tokio manages a queue of runnable items. When using async/await, tasks can pause, allowing other tasks to run on the same thread. Tokio extends this by being multi-threaded, enabling multiple queues to run in parallel.

What Makes Rust Appealing

A significant draw of Rust is the strong feeling that "when it compiles, it works." While not literally bug-free, this sentiment stems from Rust's robust type system. Compared to other languages with type systems, Rust excels in preventing common pitfalls. For instance, Java's null reference, famously dubbed a "billion-dollar mistake," can lead to frequent crashes. Rust, however, forces explicit handling of potential nullability, preventing such errors at compile time.

Rust also eschews exceptions for error handling, instead returning errors as values. This is managed through an enum that can be either a Result or an error. The ? operator simplifies error propagation: if a function returns an error, the ? operator automatically returns that error from the current function. This makes error handling explicit and less prone to being overlooked, unlike exceptions which can be implicitly handled.

Documentation in Rust is also a standout feature. Comments marked with three slashes (///) become documentation comments. Crucially, examples embedded within these comments are automatically compiled and run as tests. This ensures that documentation examples remain synchronized with the actual code, preventing outdated or broken examples.

Furthermore, Rust prevents uninitialized variables, requiring them to be assigned a value before use. Libraries like Serde leverage macros to generate efficient code for deserializing data, such as JSON, ensuring that incoming data conforms to the expected structure and types. The match statement, similar to switch statements in other languages, is exhaustive for enums. If a variant is missed, the compiler will flag it as an error, forcing developers to handle all possibilities or explicitly include a catch-all case. This design philosophy extends to refactoring, where the compiler guides developers to update all necessary code locations after changes.

Rust for TypeScript Engineers

For developers coming from TypeScript, Rust is an excellent fit for backend development, API servers, and other server-side applications. The primary pitch is reliability: Rust aims to minimize bugs, preventing the kind of issues that might cause developers to be woken up in the middle of the night. Beyond the absence of null pointer exceptions and robust error handling, Rust's type system and explicit error management contribute significantly to this reliability.

Moving from C++ to Rust

The pitch for C++ developers is even stronger, primarily due to Rust's memory safety. In C++, small mistakes like off-by-one errors in array indexing can easily become security vulnerabilities. Rust eliminates this entire class of bugs.

Memory Safety

Memory safety ensures that code, no matter how flawed, won't suffer from a specific set of bugs that often lead to security vulnerabilities. These include reading past the bounds of an array or using memory after an object has been deallocated. In the Linux kernel, for example, a vulnerability could arise if an attacker manages to overwrite a task_struct (representing a process) with a crafted object, potentially setting the user ID to zero and gaining root privileges. Rust's memory safety guarantees prevent such exploits.

Garbage Collection Tradeoffs

Rust does not have a garbage collector. Instead, memory is managed through an ownership model where variables are cleaned up when they go out of scope. This contrasts with languages like Java or C#, which use garbage collectors to periodically reclaim unused memory.

The absence of a garbage collector offers several advantages:

Ownership, References, and Borrowing

Rust's ownership model is a core concept. Each value in Rust has a variable that's its owner. There can only be one owner at a time. When the owner goes out of scope, the value is dropped.

"Fighting the borrow checker" is a common experience for newcomers. Often, the solution involves rethinking data structures rather than just tweaking the code. For example, trying to create cyclic data structures (like a book referencing its pages, and pages referencing the book) without proper reference counting can lead to borrow checker errors.

Unsafe in Rust

The unsafe keyword in Rust acts as an "escape hatch." While Rust guarantees memory safety without unsafe, using it allows for operations that the compiler cannot statically verify. Each unsafe operation has a set of rules that the programmer must adhere to.

Common uses of unsafe include:

When unsafe code is properly encapsulated within safe APIs, it can provide the benefits of low-level control and performance without exposing the underlying unsafety to the rest of the program.

Crates and Cargo

The Rust ecosystem revolves around crates, which are Rust's term for packages. The build tool and package manager for Rust is Cargo. Cargo handles:

Unlike package managers like pip which install packages globally or in virtual environments, Cargo's dependencies are typically local to the project. This isolation helps prevent conflicts between different projects.

Linus Torvalds has expressed reservations about Cargo downloading and running code from the internet, preferring the security guarantees of distribution-specific package managers. While the Rust ecosystem has not seen the same level of supply chain attacks as some others (like npm), the risk of malicious crates exists for any internet-connected package manager.

Ecosystem Maturity

The Rust ecosystem is most mature for backend services and command-line tools. While efforts are underway to use Rust for front-end development via WebAssembly, it's generally recommended to pair Rust backends with TypeScript frontends. Rust is also seeing significant adoption in embedded systems and operating system components, including the Linux kernel.

Language Design and Governance

The Rust project is governed by various teams, including the Language Team, Library API Team, and Dev Tools Team. Unlike languages with a single benevolent dictator, Rust's decisions are made through consensus among these teams.

Getting Paid to Work on Rust

While the Linux kernel has a large number of contributors paid by companies to work on it, the Rust project also sees significant corporate support. Many developers are employed by companies to work on Rust, and the Rust Foundation offers grants to support full-time work, particularly for students and those new to open-source contributions.

Contributing to Rust

For those interested in contributing to Rust, resources include:

Rust in the Linux Kernel

The integration of Rust into the Linux kernel has evolved significantly. Initially met with skepticism, Rust is now officially recognized as a supported language within the kernel, no longer considered experimental. This shift reflects the growing confidence in Rust's ability to provide memory safety and reliability for critical system components. Government regulations, such as those from the US Department of Defense, are also pushing for the adoption of memory-safe languages, further bolstering Rust's appeal.

AI Use Cases for Rust

AI tools are beginning to find their place in the Rust ecosystem. While still learning how to best leverage them, developers are finding them useful for tasks like generating code snippets, writing benchmarks, and even assisting with code reviews. The strong compile-time guarantees of Rust make it a promising candidate for AI agents, as they can receive more immediate feedback on correctness. The Linux kernel community, for instance, is exploring AI for code reviews, seeing it as an additional safety net rather than a replacement for human oversight.

Learning Rust

The official Rust book is an excellent starting point for learning the language. For more intermediate developers, John Gant's book offers deeper insights. Practical projects are also crucial for proficiency. While AI can help generate code, it's essential to understand the underlying concepts, especially Rust's ownership model and the reasons behind compiler errors, to avoid a false sense of comprehension.

Favorite Rust Features

A particularly exciting feature currently being developed for Rust is "in-place initialization." This allows for constructing values directly where they are needed, preventing them from being moved afterwards, which is crucial for certain low-level programming scenarios like in the Linux kernel.

Book Recommendations

Key Takeaways