“When it compiles, it works” in Rust

By | May 12, 2025

People often claim that with Rust code “when it compiles, it works”. Like most programming aphorisms, it is a slight exaggeration. This is not actually a claim that it is impossible to write bugs in Rust. It is not a claim that any program written in Rust is bug free.

It is meant as a statement of how much the compiler helps you to avoid certain classes of bugs. (Not just the obvious memory bugs that most focus on.) Rust’s type system, especially Result<T,E> and Option<T>, can be used to define some problems away. Rust’s memory model and borrow checker make many forms of memory and concurrency bugs much less likely. Many in the Rust community have made better arguments than I will for these features.

I want to propose a somewhat different take on the statement.

Maintenance

It has been said > 80% of the lifetime of a program is spent in maintenance. In over 3 decades as a professional programmer, I have spent most of my time maintaining code. Some written by me, most written by others. I’ve worked in compiled languages, dynamic languages, client/server software, web back-end code, and a little front-end code. It’s fair to say that I’ve seen a lot of code of various levels of quality.

Making changes to production code written by multiple programmers over a period of time is not for the faint of heart. Making those changes without major impact elsewhere is very much harder than it should be.

Any time I had to make any substantial changes to Rust code, or significant refactoring, I find that when it does compile, I know that the change is almost completely correct. That is not always the case with dynamic languages. I’ve even had cases where I missed a change in a compiled language and the code still compiled. This does not mean that I can skip testing or that I cannot have made a mistake. What it means is that quite a few of the kinds of mistakes I’ve seen in the past are handled by the compiler’s static checks.

What it does mean is that I can focus on making sure I didn’t make logical errors or misunderstand the intent of the code (at least when that intent has not been coded into types). I don’t have to spend nearly as much time on the kinds of mistakes the compiler will catch for me.

Rust’s requirement that all paths in a match statement are covered is another piece of “when it compiles, it works”.

Silly Mistakes

A lot of mistakes made when maintaining code boil down to simple, obvious, almost silly mistakes.

For example, we have a set of possible values for a variable or type, somewhere we make decisions on that set of values. If you change the set of possible values, you need to fix everywhere you might make a decision on that set. In Rust (and many other languages), that set might be coded as some kind of enumeration or enum.

The designers of Rust decided that the standard code for dealing with the variants of an enum should by default require an exhaustive test of all variations. This means that if you change the definition of an enum to include more variants, any code that match on that enum will not compile until you handle the new case. There are ways around that, but the default is to test them all. More importantly, if you work around this case, you must do it explicitly. That explicit behavior is something that you can search for in the code.

Replacing null/nil checks with Option<T> also means that if a value is optional, your code must explicitly deal with it. If a change during maintenance results in some code returning an optional value, where before it could never fail, the code won’t compile until you’ve dealt with the fallout of this change everywhere it matters. You can work around it, but the changes to do so are explicit and possible to search for.

I could go into Result<T,E> and other parts of Rust and make similar arguments, but others have done that better.

The key takeaway in this is that changes that could have wide-ranging effects when maintaining code have explicit behaviors that the compiler can check for you.

In most languages, if you change a low-level function that returns a value to one that only might return a value, it can be a major exercise to track down everywhere that value is used and verify that the null case is handled (or you could hope it was already done). The same applies to adding a new variation to whatever the language calls enumerations.

If you have a piece of code that was always successful and after a change could now throw an exception, walking the call tree to verify that the exception is handled correctly can also take quite a bit of time.

Worse, in most languages, finding any of these problems requires searching the code for something that isn’t there.

  • Where did we not check for nil/null?
  • Where did we not check for an exception?
  • Where did we not check for a new variant?

So, the main thing that “if it compiles, it works” refers to is this kind of issue. Errors that are hard for a human to find are automatically caught by the compiler, unless you have explicitly told the compiler not to. And, if a new bug shows up related to that change, you can search for the patterns used to explicitly bypass the compiler and relatively quickly solve that issue.

Now I am back to only working on what I might have screwed up. That is a major help when it comes to maintaining code.

Disclaimer

In fairness, I have to point out that my experience with the difficulties inherent in maintaining code are based on code that has been in production for years. Changing code that has been modified by a number of developers over 5-10 years is naturally going to contain differences than code that was written by one developer over a period of one or two years.

Despite that fact the kinds of errors I see being caught by the Rust compiler match pretty well the kinds of errors I’ve seen in code in other languages. Some of those errors have remained in code were not caught at the point of the change and some lasted for months or years before being fixed. Most of those bugs were subtle or only manifested in some edge case. But, they were still there.

Leave a Reply

Your email address will not be published. Required fields are marked *