Why I'll Never Shut Up About Rust

·9 min read
rusttypescriptprogrammingjakuta

Why I'll Never Shut Up About Rust

Anyone who's spoken to me for any amount of time in the tech world knows about my love for Rust. The funny thing is, that love was sparked before I even knew what the language was. Before I even knew what statically typed languages were. Call it ignorance, call it the natural arc of being self-taught, but I started with Lua.

Lua

Lua was my first language and it'll always have a soft spot in my heart. It reads like plain English, it literally tells you when a block is done with end, and it got me hooked. But even back then, my ambitions were way bigger than my tools. I wanted to build the most complex thing I could think of, as fast as I possibly could, at enterprise scale. And the problem was bugs. Just, constant bugs. I thought that was normal. I genuinely thought runtime errors were a fact of life, something every programmer dealt with, and there was no way around it.

You don't know what you don't know. But I had one thing going for me. My goal was never to be the best programmer of all time. It was to create great programs. That kept me humble enough to look at what I was writing and go, yeah, this is spaghetti.

The iteration loop from hell

The biggest issue was determinism. Or really, the complete absence of it. I was doing game development at the time, and the feedback loop was miserable. Write a line of code. Launch the game. Play through to the point where the code actually runs. Find the error. Fix it. Relaunch. Repeat. Sometimes for hours over one function.

The only defense you have in a dynamic language is guards. Check for nil, validate the type, make sure this number isn't negative, make sure this table actually has the field you think it has.

function withdraw(wallet, amount)
  if wallet == nil then return end
  if amount == nil then return end
  if type(amount) ~= "number" then return end
  if amount <= 0 then return end
  if wallet.balance == nil then return end
  -- five guards later, the actual logic
  wallet.balance = wallet.balance - amount
end

Look at that. Half the function is defensive code. It has nothing to do with the problem. And the worst part isn't even the verbosity, it's that you have to remember to do this everywhere. You're holding two mental models at once: how should this work, and what could go wrong. Which, fine, that's a useful skill. But if I'm already going to think that hard about failure modes, I might as well put that thinking somewhere the computer can check for me.

That's what pushed me into static types. TypeScript was first.

TypeScript, or: getting halfway there

I hated it at first. Everyone knows the baggage. Java to JavaScript to TypeScript, each layer leaving behind patterns nobody would design from scratch. And TypeScript had types but it didn't always enforce them, so I couldn't even see the point. Remember, I still thought catching bugs at runtime was just how it worked.

Then it started clicking. Every function signature told you what it expected. If I passed a string where a number goes, I'd know before the program even ran. Not at 3am. Not in production. At compile time. The bugs I'd been catching at runtime were just... gone. Or at least, the category of "wrong type in the wrong place" bugs were gone.

And then I discovered the escape hatches.

function processPayment(amount: number, currency: string) {
  const rate = getExchangeRate(currency as any);
  return amount * rate; // NaN at 3am when currency is undefined
}

as any. Non-null assertions. Every place where TypeScript lets you say "trust me, I know what I'm doing." Those are exactly where bugs hide. And I don't mean theoretically. It took a multi-day debugging session, me and multiple senior engineers at Jakuta Inc., to track down a bug that was hiding behind one any cast. Days. Senior engineers. One escape hatch. That was the incident that made us ban dynamic languages outright. In our style guide now, escape hatches are heavily restricted, and on some projects, impossible.

After that I was pretty satisfied with TypeScript. Verbose, sure, but expressive. Huge community. I was catching most of my bugs before runtime. I thought this was as good as it gets.

Then I found Rust

And my whole mindset shifted.

Everything I'd ever wanted from a programming language, Rust just... had. Not as a plugin. Not as a lint rule you configure. As a first-class part of the language, from day one. In Rust, your code can be correct by construction. Not "mostly correct" or "correct if you're careful." Undefined states are impossible to represent. If your program compiles, the only bugs left are logic bugs, mistakes in what you told the computer to do rather than mistakes in how the computer runs it. And once you fix those, that's it. There's nothing else lurking.

The speed, near C/C++ performance. The tooling. The compiler, which gives you error messages so good it feels like pair-programming. (Seriously, I'd argue Rust is one of the better languages for beginners purely based on how much the compiler teaches you.) WebAssembly support. All extra credit. I'd love Rust if it was slow and ugly, just for the strictness.

And here's the thing about TypeScript's escape hatches. They exist because TypeScript is a superset of JavaScript. It inherited a dynamic language's sins and has to live with them. Rust was designed from scratch with type safety and memory safety as hard constraints. There's nothing to escape from.

Algebraic enums, though

I need to talk about these specifically because they're the feature that rewired my brain.

enum TransactionResult {
    Success { new_balance: u64 },
    InsufficientFunds { available: u64, requested: u64 },
    AccountFrozen { reason: String },
    DailyLimitExceeded { limit: u64 },
}

When you match on this, the compiler won't let you move on until you've handled every variant. You physically cannot forget AccountFrozen. You can't silently swallow DailyLimitExceeded. Every state is accounted for.

Once you layer on unit tests and integration tests, you can say something impossible to say in Lua and hard to say honestly in TypeScript: if it compiles, it's correct.

I can never go back to not having this.

The Zen Commandments

At Jakuta Inc. we have a set of rules called the Zen Commandments, inspired by the Zen of Python. Side note, I've always found it funny that the Zen of Python describes principles that a statically typed language actually enforces, but Python just... suggests them.

"Explicit is better than implicit." "Errors should never pass silently." "In the face of ambiguity, refuse the temptation to guess."

Suggestions in Python. Law in Rust.

We have a bunch of these, but two of the Rust-specific ones are worth explaining.

Booleans are banned

Yeah, fully. Booleans are Turing complete but they're terrible for modeling real domains because they let invalid states compile without complaint.

// this compiles and makes zero sense
struct Wallet {
    is_filled: bool,
    is_empty: bool,
}
// is_filled: true, is_empty: true. nobody stops you.
 
// what we do instead
enum WalletState {
    Empty,
    Funded { balance: u64 },
}

Simple example, I know. But we found this class of bug constantly in real production code. Two flags that should be mutually exclusive both set to true. Three flags where only some combinations are valid and nothing enforces which ones. Yeah, writing an enum is more code. But code is written once and read a thousand times. We'll take the extra lines.

Options are banned too

This one makes people uncomfortable at first. New engineers push back for about a month, then they never want to go back.

We ban Option in our domain models. Same reasoning as booleans: optionality creates incoherent states.

// what does verified: Some(true) with email: None mean?
// what does email: Some("zee@jakuta.com") with verified: None mean?
struct User {
    name: String,
    email: Option<String>,
    verified: Option<bool>,
}
 
// every state is intentional
enum User {
    Unverified { name: String, email: String },
    Verified { name: String, email: String, verified_at: DateTime },
    Anonymous { display_name: String },
}

More thinking at design time. Less debugging at 3am. That's the trade, and we'll make it every time.

Pull requests

Our Rust PRs are strict. I won't sugarcoat that. Rarely is anything waved through. But we hold that line because Rust actually gives you the tools to get it right at compile time, for a reasonable cost. In TypeScript I can sometimes understand a pragmatic shortcut. In Rust there's no excuse. When you're building fintech or security tooling, "pragmatic" shortcuts are just deferred production incidents in my book.

The evangelism thing

We all know the stereotype. Rust programmers don't shut up about Rust. One Reddit search and you'll find a hundred threads about it.

Guilty as charged. But there's a reason, and it's not just tribalism. The more people write Rust, the more libraries exist, the more jobs open up, the better the tooling gets. Every convert makes the ecosystem better for everyone already in it. The evangelism is self-interested in the most honest way possible.

But also. I heard a quote once, paraphrasing: I don't care how fast you can start a project. I care how confidently you can finish one.

If you're building something real and you're tired of runtime surprises, try Rust. Microsoft is rewriting parts of Windows in it. Cloudflare built Pingora to replace Nginx. Discord moved performance-critical services over from Go. Amazon wrote Firecracker in it. The Linux kernel accepted it as the first new language in decades.

They all did it for the same reason that hooked a self-taught kid writing Lua scripts for game dev: the promise that your code can be correct, provably, before it ever runs.


Big shoutout to No Boilerplate on YouTube. Their video Rust Data Modelling Without Classes was a huge inspiration for this post and for how I think about data modeling in Rust in general. If you want to go deeper on anything I talked about here, start there.

And if you're getting into Rust, read the official Rust documentation. I'm serious. It's the best language documentation I've ever read. Most docs feel like a reference manual you skim when you're stuck. The Rust Book reads like a course taught by someone who actually wants you to understand. Great docs befitting a great community.