Why Rust Feels Different: Alice Ryhl on Reliability, Ownership, and How the Language Is Governed

Open on YouTube ↗
Overview

Rust has a reputation as one of the most admired programming languages and also one of the hardest to learn. In this episode of The Pragmatic Engineer Podcast, Alice Ryhl talks through what makes it distinctive. Ryhl is a software engineer on Google's Android Rust team, a core maintainer of Tokio (the most widely used async runtime in Rust), and a Rust language team adviser. The central question is why developers stick with a language that has such a steep learning curve. Ryhl's answer is that Rust is built around catching the mistakes programmers keep making, and that it combines this reliability with the ability to run in low-level contexts like the Linux kernel. The conversation also covers how Rust is governed without a single leader, how features move from proposal to stable release, how Rust is progressing inside Linux, and where AI tools fit into Ryhl's work.

30 min read

From Minecraft mods to Tokio maintainer

Ryhl's path into programming began with Minecraft. They wanted to write their own mod, so they learned Java. The modding didn't get very far, but the programming stuck. That happened just before high school. After high school, Ryhl worked full-time as a software engineer for a year, then did a three-year bachelor's degree and a two-year master's. They joined Google part-time when the master's program started and switched to full-time after finishing.

Ryhl's involvement with Rust started long before Google. During school, they spent a great deal of time on the Rust users forum answering questions, and estimates they have written around 10,000 posts there. Later they became active in chat servers, including the Discord server for Tokio. They kept answering questions there too, and when the same questions came up repeatedly, they fixed the documentation. That documentation work is how they came to maintain Tokio.

Ryhl describes Tokio as an asynchronous runtime, roughly "the standard library for Rust when you're using async." Their comparison is JavaScript in the browser. The browser has an event loop holding tasks that are ready to run, and it executes them one after another. With await, one task can pause while another runs on the same thread. Tokio also keeps a queue of runnable work and runs it. The difference Ryhl highlights is that Tokio can be multi-threaded, with several queues running in parallel.

"When it compiles, it works"

Asked what drew them to Rust, Ryhl points to a feeling many Rust users describe: once the code compiles, it works. They stress that this belongs in quotes. Bugs obviously remain possible, and the saying is not literally true. Still, Ryhl thinks there is a real reason people say it.

The starting point is a type system, which Ryhl considers a prerequisite for any language to feel this way. But Ryhl argues Rust does better than many other typed languages. The classic example is null in Java. Ryhl recalls that Tony Hoare, who invented null, called it his "billion dollar mistake," because any function call could potentially crash the program. In Rust, a value that might be absent has to be marked explicitly as such, and the compiler then forces you to check before using it. You cannot forget the check the way you can in Java.

On Rust's growth, the host notes that the language began around 2006 and seems to be appearing in more and more places, citing Oxide as a company that went all-in on Rust. Ryhl mentions, with some amusement, having recently checked the TIOBE index and found that Rust had overtaken PHP and Go there.

The pitch for TypeScript developers

Ryhl says the case for Rust depends heavily on which language you are coming from. For TypeScript developers, Ryhl sees Rust as a backend language, a good fit for backends and API servers, and would not use it for the frontend. The argument is about sleep: you don't want to be woken at night because your web server has problems, so you want a language with as few bugs as possible. Zero would be ideal but is hard to achieve. "As few bugs as possible" is how Ryhl sums up the idea of Rust.

Beyond null handling, which Rust does through its enum type (Ryhl acknowledges TypeScript can do something similar and is fairly good on that front), Ryhl highlights several features.

Error handling. Rust largely doesn't use exceptions. A function returns the error as a value, using an enum that is either the result or the error. The ? operator, placed after a call, means "if this fails, return the error." Ryhl's point is that this is very easy to use but not invisible the way exceptions are. It takes one character, not zero. If you forget the ?, you get a compilation error. You can also handle the error manually. The goal is to eliminate the situation where an implicit error condition you never considered takes down your server.

Documentation tests. A comment becomes a documentation comment when it starts with three slashes instead of two. Examples written in that documentation are turned into tests. If you change the underlying code and break an example, the test fails, so documentation examples cannot silently drift out of date. The host calls this the first real solution they have heard to a problem developers have complained about for over a decade.

Initialization. Rust will not let you use a variable before a value has been assigned to it.

Parsing input. Ryhl describes the Serde library. You define a struct with typed fields and declare that it should be parseable from, for example, JSON. Serde is a macro that generates code checking that the JSON has the right shape: all required fields present, with the correct types. It generates native code specific to that JSON shape, which Ryhl says makes the checks very efficient.

Exhaustive matching. Rust's equivalent of a switch statement is match. You write a branch for each enum variant, and missing one is a compiler error. A catch-all case is allowed, but most of the time you list every case. When a new variant is added later, the compiler points to every place that needs updating.

That leads to what Ryhl sees as a broader reliability benefit: refactoring. Ryhl describes changing something like a return type and then fixing compiler errors "until the compiler stops shouting," at which point every affected place has been updated. The host remarks that the language designers seem to have thought hard about what typically goes wrong in other languages and tried to fix it in the compiler. Ryhl agrees that the pattern shows up everywhere: if you get something wrong, either the code won't compile or at least there is a lint for it.

The pitch for C++ developers: memory safety

Ryhl thinks the argument is even stronger for C++ developers. In JavaScript, a mistake might take down your server, which is bad enough. In C++, Ryhl says, a mistake is a security vulnerability most of the time. Something as trivial as an off-by-one on an array can become exploitable, and "this just keeps happening."

Ryhl defines memory safety as the property that no matter how stupid the code you write is, it will not have a certain class of bugs. That class is the kind that usually leads to security vulnerabilities, such as reading past the end of an array into random memory, or destroying an object and using it afterward so that you are touching memory now belonging to some other object.

Ryhl gives a classic kernel exploitation example. Suppose an object is freed and its memory reused for a task_struct, which essentially represents a process and has a user ID field. Code writing zeros to memory is common. If a stale reference causes a zero to be written into that user ID field, the process is now root. From there an attacker can take over the machine, and as Ryhl puts it, once you're root, you've lost.

The host summarizes the C++ pitch as memory safety eliminating an entire class of bugs that turn into some of the most serious threats software faces. Ryhl adds that this comes on top of all the reliability features already discussed for TypeScript developers.

Garbage collection and why Rust avoids it

For Ryhl, Rust's uniqueness lies in the combination: it is reliable, yet it has no garbage collector and is usable in low-level contexts like the Linux kernel or firmware.

Ryhl explains the difference this way. In Java or C#, a piece of code periodically checks all your objects, determines which are no longer used, and cleans them up. In Rust or C++, a value is cleaned up when it goes out of scope. For embedded use cases, a periodic collector may be impossible or unacceptable. Ryhl argues it can also matter on the backend: if a request arrives while the collector is scanning all objects, you get a latency spike and the response takes much longer.

As for what Rust code looks like, Ryhl says it resembles many other languages. It uses braces and semicolons rather than Python-style indentation, so someone who knows similar languages can read it and get a rough idea of what it does.

What trips up newcomers: data structures

Asked what developers struggle with most, Ryhl names how to put data structures together. Code logic can mostly be written as in other languages, but data structures are different.

Ryhl's example is a book with an array of pages. In TypeScript, you might give each page a field referencing the book, while the book references its pages, creating a cycle. In Rust, objects usually have to be designed so they are not cyclic. They should form a tree or a DAG. Cycles become possible with reference counting, with some caveats. What happens with learners is that they try to build cyclic structures without such tools, hit a compiler error, change the code, and hit another compiler error. The common trap, according to Ryhl, is that people keep changing the code when the actual fix is to change the struct.

Ownership, reference counting, and the borrow checker

Ryhl explains ownership as the idea that when a variable contains an object, the object is only there. It is exclusive. If you write let a = some string and then let b = a, that is a move. Using a afterward is a compiler error because its contents have been moved away. This matters because there is no garbage collector. When b goes out of scope, it must clean up the string. If a were still valid, the string would be cleaned up twice, which is not allowed. In garbage-collected languages, a and b simply go out of scope and the collector cleans up later. In Rust, cleanup happens at scope exit, and ownership ensures that the moved-from variable has nothing to clean up.

Reference counting is one of several pointer types in Rust. Ryhl describes Arc: you have an object somewhere and an Arc pointing to it. Calling clone on the Arc increments a counter and gives you a second Arc sharing the same memory, however large the object is. When an Arc goes out of scope, the counter decrements, and when it reaches zero the object is cleaned up. This is the way to express that an object genuinely needs to live in multiple places with no single owner.

References are another pointer type. A reference is just a pointer to the object, with no runtime checking. That means the compiler must guarantee that the last use of the reference happens before the object goes out of scope. Creating a reference is called borrowing: the owner owns the value, and the reference is a borrow. The borrow checker enforces these rules. It also handles mutation. Ryhl's example is holding an immutable reference to element five of a vector and then calling clear on the vector. The borrow checker prevents you from using the reference to element five afterward. More generally, it ensures that a mutable borrow does not overlap with other borrows: you can have one writer at a time or any number of readers. It checks this within the scope of, for example, a function.

"Fighting the borrow checker," a phrase common on forums, describes being unable to get out of these compiler errors. Ryhl again traces this mostly to struct design. A common mistake is putting a reference inside a struct. Rust assumes it can verify the reference's scope by looking at a single function, but if the struct is passed between functions, that analysis may not be possible and you get a compiler error. The solution is often a different pointer type, frequently the reference-counted one. Once again, the answer is to change the data structure. The host notes this explains why Ryhl singled out data structures as the main hurdle.

Unsafe: an escape hatch for building new features

Rust has a keyword called unsafe, and the host asks what it is and why it exists at all.

Ryhl calls unsafe the escape hatch. Rust's guarantee is that if you use no unsafe, then no matter how stupid your code is, it will never have the bugs that typically become security vulnerabilities. With unsafe, some guarantees remain but they are weaker. Each unsafe operation has a list of rules, and violating them can lead to those bad bugs. Ryhl emphasizes that unsafe does not disable the borrow checker or anything like that. It only unlocks some additional operations that are not safe in general, so you have to verify their correctness yourself.

The vector example illustrates this. Normal indexing with brackets checks the length. Accessing element five requires a length of at least six, and otherwise the program crashes. In very performance-sensitive code, perhaps in a loop, you may want to skip that check. Vector provides a separate function, get_unchecked, which returns the element without checking the length. Its signature is marked unsafe fn, and such functions can only be called from an unsafe block. All an unsafe block does is allow calling functions marked unsafe.

In practice, Ryhl says, unsafe usually doesn't appear at all in something like a backend server. Its main purpose is adding new features to the language. Imagine Rust had no vector type, but did have functions to allocate memory, free memory, and read from a pointer. You could write a Vec struct containing a raw pointer (unsafe to use), a length, and a capacity, with private fields so nothing outside the module can touch them. If anyone could simply set the length field to 20, that would be bad. Using field privacy and careful API design, you can build a vector that safe code can use without being able to break it. Whatever mistakes a user makes, the result is a crash or a proper length check, not memory corruption. "As long as you design your API right," Ryhl says, "you can add new language features by using unsafe." The other classic use is calling into C libraries: you can wrap a C library in a safe API that enforces calling it only in correct ways.

Crates and Cargo

A crate is simply Rust's word for a package. Rust's tool Cargo does a lot in one place. cargo run compiles and runs your code. The Cargo.toml file specifies dependencies and other crate information, and running the project downloads dependencies into what's called a registry, which is basically a directory of every crate you've downloaded. cargo test runs tests, including documentation tests. cargo doc generates documentation. Cargo also supports benchmarks and examples. You don't need separate tools for fetching dependencies, running code, and generating docs.

The biggest difference from something like pip, in Ryhl's view, is that nothing is installed onto your machine globally. With pip, you either install packages globally or use virtual environments. With Cargo, everything is local to the project. The only shared piece is the registry, which exists to avoid downloading the same thing twice.

Ryhl shares an anecdote about Linus Torvalds. At the Linux Plumbers Conference, Torvalds wasn't seen during the entire event until he appeared at the very end of the social event. When Ryhl went to say hello and mentioned working on Rust, he immediately started explaining that he didn't like Cargo. His reason: Cargo downloads code from the internet and runs it, like any package manager, and he doesn't want anything on his computer doing that other than his distribution's package manager.

The host raises supply-chain attacks, which have been a growing problem in the Node ecosystem. Ryhl acknowledges that in principle Cargo has the same exposure. If someone got hold of Ryhl's keys for publishing Tokio releases, they could upload a malicious version. Ryhl doesn't think Rust has had many problems with this compared to npm, but isn't sure, and calls it a hard problem, particularly when someone can impersonate a library maintainer. Ryhl believes the ecosystem does things like delete malicious crates.

Where the ecosystem is mature and where it isn't

Ryhl considers the frontend the least mature area. There have been attempts to compile Rust to WebAssembly and run it in the browser as a TypeScript replacement, but if Ryhl were building a web application, they would use Rust on the backend and TypeScript on the frontend, not the WebAssembly route. Backends are a great fit, and so are command-line tools. Rust is also expanding into Linux and many embedded projects. Ryhl notes that some C codebases are starting to say that since Rust made it into Linux, maybe they should adopt a memory-safe language too, and mentions that Git and CPython are having that discussion, likely among others.

Governance without a benevolent dictator

Rust originated at Mozilla but, Ryhl notes, is no longer a Mozilla project at all. Today there is the Rust project, made up of teams: a language team for the language itself, a library API team that decides on the standard library's API, a compiler team, and others. The people on these teams run the language.

Unlike Python or Linux, Rust has no benevolent dictator for life. Ryhl's description of how decisions get made is blunt: if the team doesn't sign off, it doesn't happen. Particularly contentious or large issues would probably be discussed in person, for example at Rust Week, which includes the Rust All Hands, an event that brings Rust developers together in one place. The host contrasts this with companies, where team boundaries are often set top-down. Ryhl says that in language design, the teams really are the structure, and in Ryhl's experience they have been good at delegating, sometimes saying that something is a libs-API decision and they will go with whatever that team decides.

The RFC process and final comment periods

Big decisions go through RFCs (requests for comments). Ryhl has written one, for a feature called "derive smart pointer," which makes some standard library types less special. The idea is that you write the RFC before implementing, though people sometimes start implementing early. The host notes that the same thing happened with RFCs at Uber.

Ryhl thinks the RFC template is quite good. After the summary, the first important section is motivation: why the feature should exist. Two sections Ryhl finds especially interesting are the guide-level explanation and the reference-level explanation. In the first, you explain the feature as if writing a tutorial for it, as though it already existed. In the second, you explain it again as if it already existed, but as it would appear in the language reference. The host compares this to Amazon's practice of writing a press release before building a feature, since both force you to think from the user's perspective. Other sections include rationale and alternatives, prior art, and future possibilities. Ryhl sees rationale and alternatives as especially important because it lets you answer questions before they are asked and explain why you chose your design over others. Prior art covers questions like what C++ did.

RFCs are submitted as pull requests with a Markdown document to the RFCs repository, where people discuss them. A document written elsewhere is called a pre-RFC. Once the relevant team is happy, a process used for essentially all team decisions kicks in: the final comment period (FCP). Someone tells a bot to start the approval process, and it posts a GitHub comment with a checkbox for each team member. Once all but at most two members have checked their box, a two-week final comment period begins. Ryhl explains the rationale: if everyone else checks immediately, the remaining people still get a chance to look, while things keep moving at a sensible pace. Team members can also file a concern, which pauses the process until resolved. After the two weeks, the proposal is accepted. The same FCP mechanism can be applied to any pull request or issue, such as a change to the language reference that the team wants everyone to agree on.

From nightly to stable: feature flags and the six-week cycle

After an RFC is accepted, the feature is implemented through pull requests but placed behind a feature flag. Rust calls these nightly features: unavailable normally, but usable with the nightly build of the compiler, where people try them out experimentally.

When a feature seems ready, the pull request that removes the flag includes a stabilization report, which Ryhl describes as a relatively recent invention. It explains how the feature has been used, identifies dangerous edge cases, and points to tests for each one. The team runs the FCP process again to agree on stabilization, and then the change is merged into the main branch with no flag.

From there, the timeline is fixed. Somewhere between zero and six weeks later, a beta release of the compiler is cut that includes the feature, and six weeks after that, the beta becomes the next stable release. There is no concept of beta-only features. The beta period exists to test the upcoming stable release so problems can be fixed before it ships.

Smaller changes have lighter processes. For a new function or type in the standard library, the library team uses an API change proposal (ACP), which is just a GitHub issue describing the interface and why it should exist. If the library team approves, you implement it as unstable, then stabilize later. The compiler team has major change proposals (MCPs). Despite the name, these are for changes big enough to need an issue but not big enough for an RFC. The classic example is a new compiler flag.

Editions versus versions

Besides releases every six weeks, Rust has editions: 2015, 2018, 2021, and 2024. The key difference from versions, Ryhl explains, is that when you use compiler version 1.90, everything uses that version, but different crates can use different editions. Rust has a very strong backward compatibility guarantee. The idea is that code you write keeps working forever, and a crate can stay on its edition indefinitely.

Editions are how Rust makes breaking changes "without breaking people," for example syntax changes. When async/await was added in an edition, code on the older edition couldn't use it, and could still have a variable named async. In the newer edition you can't. Crates on different editions still work together: a library written in the 2021 edition can be used by a project on the 2024 edition.

The host suggests an edition sounds like a major version in other languages. Ryhl says the big idea is to change the language while keeping old code working. The difference from something like Python 2 versus Python 3 is that editions can be mixed and matched freely.

Ryhl recently became a language team adviser, which they describe as "language team light." The team trusts the adviser's opinion, but the adviser doesn't attend every meeting and has no FCP checkbox, though they can still file concerns. Full membership would mean weekly meetings on Wednesday evenings, which for Ryhl is normally dinner time.

Who pays for Rust, and how to contribute

The host recalls Greg Kroah-Hartman telling them that about 80% of frequent Linux contributors are employed by companies that value their work in the kernel. Ryhl considers the Linux kernel truly unique in having thousands of contributors doing it on work time. In Ryhl's view, the Rust project does quite well at having people paid by employers, but nowhere near the same extent. The Rust Foundation also offers grants. A student who wants to work on something, for example, might get funding. Ryhl thinks that's a great thing the foundation does.

For engineers who want to contribute, Ryhl suggests the issue tracker, but thinks the best starting point is usually something you personally want to change. Much of the Rust project's discussion happens on Zulip, a chat server where you can talk to people. For Rust in Linux, the Rust for Linux website (rust-for-linux.com) has a contributing page listing drivers you might work on and linking to the issue tracker. Ryhl also points out that you can contribute to the Linux kernel without touching the kernel: implementing a Rust language feature the kernel needs, or helping move the Rust-in-GCC project forward, both help Rust in Linux.

Rust in the Linux kernel: no longer experimental

Ryhl has attended Linux Plumbers for several years as part of their Rust for Linux work, and says each time it has been obvious that things had totally changed since the year before, four times in a row. One year people might see Rust as a nice little side project, and the next year they have Rust code in their own subsystem. At the most recent Linux Plumbers in December 2025, the big news was that the Linux kernel maintainer summit had agreed Rust is no longer experimental in the kernel. The host asks whether that gives Rust the same status as C. Ryhl says not the same status, but the removal of the experimental label means, in essence, "we've proved this is going to work."

The host mentions a US Department of Defense regulation barring agencies from using non-memory-safe languages. Ryhl doesn't address that specific claim but says multiple governments have made similar moves, essentially telling projects that their use of C or C++ causes an unacceptable number of memory safety vulnerabilities that simply wouldn't happen otherwise.

AI tools: feedback loops, code review, and a cautionary Makefile

Ryhl says they have been trying AI tools and are still learning how to use them. They like the Gemini command-line interface.

On whether Rust suits AI agents, Ryhl connects it to the earlier refactoring story. People often say tests matter when using AI because they let the AI check whether it did the right thing. Similarly, agents can talk to the compiler, which tells them what to fix. Ryhl thinks Rust could be a promising candidate for agents on that basis, and when the host adds that certain bugs are harder or impossible to ship, Ryhl agrees there is an aspect of that.

At the Linux kernel maintainer summit, one topic was AI for code review. Some people had set up bots that feed patches sent to the mailing list into an AI agent that posts a review. Ryhl reports that Linus and others said these reviews were actually very impressive for kernel code, and that this use case seems to excite the kernel community. Ryhl ties it back to memory safety: these bugs are trivial mistakes that can be made in a huge number of places. Even a very good programmer who makes them only 0.1% of the time will still make them, so if AI catches many of them, that's valuable. The host adds that AI might help find missing edge cases in tests, and Ryhl agrees. The host observes that this is one of the first AI conversations they've had focused on raising quality rather than just speed.

Ryhl has written a few patches with AI. It worked okay, but always required their review before going to the list. One positive experience involved a commit that made Binder faster. A reviewer asked for a benchmark, which Ryhl wasn't looking forward to writing. The AI found an existing benchmark, modified it in a clever way, ran it, wrote a Python script to parse the output file, and presented the results in a table. Ryhl's assessment: at least in that instance, it saved time, if nothing else.

Asked whether AI could give Rust learners a false sense of understanding, Ryhl says it is "totally a danger" and gives an example from the kernel build system. Ryhl asked an AI to add support for a feature, and it added the necessary Rust build flags to a Makefile. But the C side passed a few additional flags that weren't strictly required yet existed for a reason, and the AI ignored them. Any human, Ryhl says, would have asked why not add Rust versions of all the flags.

Learning Rust, and what Ryhl is working on now

For experienced engineers learning Rust, Ryhl recommends the official Rust book, freely available on the Rust website (noting, amused, that Rust calls its online tutorials "books"). Beyond that, the way to learn a language is to build a project with it, perhaps a web server. For intermediate developers who have some Rust experience and want to go further, Ryhl recommends a book by Jon Gjengset. Ryhl also likes Rustlings, which gives you unfinished Rust code to complete.

Ryhl's current favorite feature is one they are still building. The Linux kernel needed in-place initialization: the ability to construct a value while knowing where it is being constructed, so it doesn't get moved afterward. Ryhl is excited about the work to add this to the language but stresses that it is very much ongoing.

In closing, the host draws out a theme from the conversation: Rust seems deliberately designed around the question of which mistakes programmers keep making and how to eliminate them, as seen in memory safety, the absence of null, errors as values that can't be ignored, and documentation examples that run as tests. The host's hypothesis for Rust's rise is that it occupies a category that didn't exist before: a language that is both reliable and performant, usable for kernel work while also being a strong alternative for higher-level backend development.