If Your Code Compiles, It’s Probably Still Wrong

You know that sweet dopamine hit when the Rust compiler finally stops yelling at you and your code compiles? It feels like victory. But it’s a trap.

We’ve been conditioned to treat a successful build as proof of correctness. It isn’t. A clean compile just means the compiler didn’t catch your mistakes. A compiler that says ‘yes’ to everything is just a fancy text editor. If your API can be misused, it will be.

According to a fascinating new analysis from the ACM’s ICFP conference, most developers are evaluating their types entirely wrong. The real metric isn’t whether your code compiles. The real metric is: how many invalid, bug-ridden API usages still compile?

Think about it. If a user can call connect() before open() and the compiler lets them, your type system is failing you. You’re just shifting runtime crashes to production.

The solution is the Typestate and Newtype patterns. Instead of returning an Error at runtime when a function is called out of order, you encode legal state transitions directly into the type system. You make the wrong thing impossible to represent. As one developer working with these patterns noted: ‘Types are puzzles. A good Rustacean will make sure that the pieces fit to make the picture.’ You return a Ticket<Func1Done> that serves as a physical key to unlock the next function. No ticket, no function call.

But here is the tension. The more precisely your types enforce valid state transitions, the more friction you feel when constructing valid states. The type system becomes safer and more restrictive at the exact same time. The compiler transforms from a helpful assistant into a strict, unyielding puzzle master. You will spend hours fighting the borrow checker just to construct a valid state.

It is frustrating. It is agonizing. It is also the only way to write truly robust systems.

The goal isn’t to write code that works. It’s to write code that makes failure impossible.

When you use typestates, you aren’t just preventing bugs—you are eliminating entire bug classes by design. You are shifting the burden of correctness from the runtime, where customers suffer, to the compile-time, where developers sweat.

Next time you design an API, don’t just ask if it works. Ask if you can make the wrong thing unrepresentable. Because if you leave a loophole, someone will find it. If an invalid state can be represented, it will eventually be shipped to production.

Stop celebrating code that merely compiles. Start building code that cannot fail.

FAQ

Q: Isn't fighting the type system just a waste of development time?

A: No, it's an investment. You can spend two hours fighting the compiler to prove a state is valid, or you can spend two weeks tracking down a runtime crash caused by an invalid state transition in production. Compile-time friction is always cheaper than runtime failures.

Q: What's the practical implication of using Typestate and Newtype patterns?

A: You shift bug prevention from runtime to compile-time. By using types like Ticket<T> to represent state transitions, you force the consumer of your API to call functions in the exact right order. If they try to call a function out of sequence, the code simply won't build.

Q: If this is so great, why isn't everyone already doing it?

A: Because it requires you to be a strict puzzle master. Designing APIs where invalid states are unrepresentable takes serious architectural foresight. Most developers are lazy and prefer to return a runtime Error because it's easier to write, even if it makes the system fragile.

📎 Source: View Source