Skip to main content
zzoo.dev
6 min read

The Hardest Language

rustvibe-codingai

Here's something that shouldn't make sense: the programming language with the steepest learning curve turns out to be the safest one to hand to an AI.

I've been building backend services in Rust with Axum for a while now. Most of the code gets generated by AI. And not only does this work β€” it works better than the same approach in Python or JavaScript. The language famous for rejecting your code is, paradoxically, the one where AI-generated code is most trustworthy.

The conventional wisdom says vibe coding β€” describing what you want in natural language and letting AI write the implementation β€” should favor easy languages. Python has minimal syntax. JavaScript runs everywhere. Why would you pick the language where a simple function might need three lifetime annotations?

Because difficulty and safety aren't the same axis. And the thing that makes Rust hard for humans is exactly what makes it reliable for AI.

Think about what happens when AI writes bad Python. The code runs. It produces wrong results, or it crashes at 3 AM in production with a TypeError that tells you almost nothing about the actual problem. The feedback loop is terrible: generate code, run it, observe some confusing behavior, try to figure out what went wrong from a vague error and a stack trace. The AI is guessing in the dark.

Now think about what happens when AI writes bad Rust. It doesn't run. The compiler rejects it immediately and produces something like this:

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &v[0];
  |                  - immutable borrow occurs here
4 |     v.push(4);
  |     ^^^^^^^^^ mutable borrow occurs here
5 |     println!("{first}");
  |               ------- immutable borrow later used here

That's not an error message. That's a structured lesson. It names the exact file, the exact line, the exact conflict, and often suggests how to fix it. Feed that back to the AI and it knows precisely what to change. The compile-fix-compile loop converges in one or two iterations because there's no ambiguity. Compare that to undefined is not a function, which gives the AI nothing to work with.

This is the reversal nobody anticipated. Rust's compiler β€” the thing that makes beginners quit in frustration β€” is the best pair programmer an AI has ever had.

The deeper insight is about what happens when Rust code does compile. There's a saying in the Rust community: if it compiles, it works. This sounds like bravado, but it's closer to a literal truth than in any other mainstream language. The type system and borrow checker eliminate null pointer dereferences, data races, use-after-free bugs, and buffer overflows. Not by testing for them. By making them structurally impossible. The compiler's stamp of approval is a stronger correctness guarantee than most languages' entire test suites combined.

This matters enormously for vibe coding. When you're not reading every line the AI generates β€” when you're steering by vibes rather than scrutinizing syntax β€” you need a different kind of safety net. In Python, that safety net is tests you have to write and hope are comprehensive enough. In Rust, that safety net is the compiler, and it checks things no test suite can.

Consider shared mutable state, the source of the most insidious bugs in software. Two parts of your program modify the same data. The interaction depends on timing, on order of execution, on things that are almost impossible to test exhaustively. An AI generating Python might produce code with a subtle race condition that only manifests under load. You'd never catch it in development. In Rust, the ownership model makes this category of bug literally unrepresentable. If two things try to mutate the same data, the program won't compile. Not "might crash at runtime." Won't compile. The AI doesn't need to understand concurrency theory. The compiler enforces it.

The strong type system does something similar but broader. When a function signature says it takes a &[u8] and returns a Result<Response, AppError>, the AI can't accidentally pass it a string or forget to handle errors. The type system constrains the solution space so tightly that there are very few ways to write code that compiles but doesn't work. In JavaScript, the AI has infinite freedom to produce plausible-looking code that silently does the wrong thing. In Rust, most of that freedom is gone β€” and that's a feature, not a limitation.

There's a performance argument too, though it's almost a bonus at this point. When you vibe code in Python, the AI writes the obvious implementation. It's usually slow. When you vibe code in Rust, the AI also writes the obvious implementation β€” but Rust's zero-cost abstractions mean the obvious implementation is fast. Iterators compile to the same machine code as hand-written loops. There's no garbage collector introducing latency spikes. You get C-level performance without either you or the AI thinking about performance at all.

I used to think the case against Rust was strong. The learning curve isn't worth it for most applications β€” that was the standard line, and it seemed reasonable. But that argument rested on a hidden assumption: that humans had to climb the curve. That every developer needed to internalize lifetimes and trait bounds and the intricacies of the borrow checker before they could be productive.

AI changed the premise. The developer doesn't need to understand lifetimes. The AI doesn't need to understand lifetimes either, not really. The compiler understands lifetimes. The compiler has always understood lifetimes. The question was never whether the rules were too strict β€” it was whether humans could hold all those rules in their heads simultaneously. That question is now irrelevant.

What you're left with, once you remove the learning curve from the equation, is a language that gives you memory safety, fearless concurrency, and native performance with no garbage collector β€” essentially for free. The cost that used to make Rust prohibitive for most projects has been absorbed by the tooling.

The hardest language turned out to be the safest one to vibe code in. Not despite its difficulty, but because of it.