โ† Writing

The compiler reviews
what you won't.

The case for Python and JavaScript was always developer time. Agents made typing free, which quietly removed the argument and left a stricter language holding something more valuable.

For twenty years the answer to "why not use something stricter?" was the same: because I have to write it, and life is short. Ceremony was the cost, expressiveness was the benefit, and a language that demanded six lines of error handling was a language that slowed you down.

That objection priced one thing. Typing. And typing is now the cheapest input in the process.

This is a companion to an argument I made about containment, which is that you cannot review your way to safety at machine speed, so your guarantees belong somewhere that enforces them without you. A container is one such place. A type system is another, and it is the cheapest one you already own.

01 The performance argument is mostly over

Start with the boring version of the question, because it clears the ground. On AWS Lambda, a Rust function compiled to a static binary on provided.al2023 initialises in roughly 12 to 22 milliseconds. Node lands somewhere between 200 and 400 at the median, worse once you pull in a fat dependency tree.1 That is a real user-visible win on spiky, low-traffic APIs.

Warm execution is a different story, and the honest answer is that it depends entirely on what the function does. Glue code that calls DynamoDB, reshapes the result and returns JSON is a wash: you are waiting on a network for tens of milliseconds and the language contributes single digits either way. Genuine CPU work, image resizing, parsing large payloads, compression, crypto, is commonly several times faster in Rust, and more than that against JavaScript that never runs hot enough for the optimising compiler to kick in. Short Lambda invocations are exactly the case where that happens.

AWS's own attempt to close the gap is instructive. LLRT is a JavaScript runtime written in Rust on top of QuickJS, deliberately shipped without a JIT, claiming around ten times faster startup and roughly half the cost. One test dropped a simple function's init from about 750 milliseconds to 55.2 The tradeoff is deliberate and it is the interesting part: no JIT means it is notably worse than Node on heavy compute. It is also still labelled experimental, so treat it as evidence about where the costs actually live.

The practical read has not changed much. If your functions are glue around cloud services, the performance case for switching mostly evaporates. So if the argument stopped at speed, it would stop here.

02 What a strict language actually buys

The memory-safety pitch is the wrong one for cloud work. A Node Lambda is garbage collected and single threaded, so use-after-free and data races were never your problem. You would be buying armour against a threat you do not face.

The properties that do transfer are quieter and are really about API design. Failure appears in the signature, so a caller cannot accidentally not know that something can fail, the way it can with a function that throws from three layers down. Absence becomes a value you have to open before you can use what is inside. And exhaustive matching means that adding a variant to an enum turns into a compiler error at all twelve places that now need updating.

That last one is the whole essay in miniature, so it is worth being precise about why. It is a property that holds during refactoring, performed by a mechanism that does not get tired, skim, or assume the other eleven call sites were probably fine.

03 Agents fail in exactly the shape a compiler catches

Here is what changed. Agents produce code that is fluent, idiomatic, confident and occasionally wrong in ways that read perfectly. That is a specific failure mode, and it maps almost exactly onto what a strict type system rejects mechanically before a human is ever involved.

A permissive language will happily accept a great deal of that. A stray type assertion, an unvalidated parse, an exception swallowed two layers down, a switch missing its new case. All of it compiles and you find out in production. The stricter language refuses, immediately, in a loop that costs nobody's attention.

A type system is an oracle. An oracle is worth most when the author of the code does not really understand the program, which is the situation you are in every time an agent hands you nine hundred lines. So the pitch changes shape. It stops being "Rust protects developers from their mistakes" and becomes "Rust protects you from your agent's mistakes", which is a stronger claim, because agents make more of exactly the kind of mistake a compiler catches.

The verbosity objection dies at the same time. Nobody minds six lines of error handling when a model writes them. Nobody is personally irritated by the borrow checker when the thing being irritated is not a person.

04 Four things that push back

Compile time inverts. A three-minute build was an annoyance to a human who could go and read something else. Inside a twenty-iteration agent loop it becomes the dominant cost, an hour of wall clock spent waiting. This one genuinely gets worse, and it is the argument I find hardest to wave away.

Training data is lopsided. There is far more JavaScript and Python in the world than Rust, and the gap is widest exactly where Rust is hardest: async lifetimes, pinning, trait bound propagation, the current versions of the async ecosystem. Models are correspondingly weaker there. That is also the region where a stuck agent escalates to a human, who now has to parachute into unfamiliar async code they did not write. An expensive failure mode, and the one people underestimate.

Compiling is not correctness. Code that satisfies the borrow checker can still panic. Unwrapping an empty option, indexing past the end, integer overflow wrapping silently in release builds. Agents reach for the escape hatch constantly, because it is the path of least resistance past the type system. Ban it in lint for non-test code, or the guarantee is decorative. Reaching for shared mutable state to escape the borrow checker, rather than because the design calls for it, is the same problem wearing a nicer coat.

Ecosystem thinness fights the other rule. Preferring existing libraries over generated code is one of the strongest things you can do with an agent, and it is in tension with picking the smaller ecosystem. Rust's core cloud stack is genuinely good. The long tail is thin: a payment provider's SDK, a feature-flag service, some analytics vendor. Where the crate does not exist, your agent cheerfully writes one, and now you own an unaudited integration with someone's billing API. That is the opposite of what you wanted.

05 The rule is strictness, not a language

None of this resolves into "rewrite it in Rust". The transferable move is to turn whatever you are already using up to its strictest setting, and let the compiler do the reviewing you were never going to do.

TypeScript's real weaknesses are not expressiveness. Its type system is very capable, in some ways more so than Rust's. The problems are that its types are erased when the program runs, so every guarantee you wrote is gone by the time real data arrives, that a signature tells you nothing about what a function throws, and that exhaustiveness checking arrives only if you impose it on yourself.

The first of those is the one you can fix, and you should fix it regardless of this whole debate. Strict mode. No unchecked indexed access. The any escape hatch banned in lint. Schema validation at every boundary where data enters the program, because a type that is erased at runtime is a promise you made to yourself, and parsing JSON hands your agent a value the compiler will believe anything about. Do that and TypeScript closes most of the practical gap.

  • Runtime validationDo it anyway.Every boundary, every language. The cheapest wall on this list.
  • Strictest compiler flagsFree.You already have them. Most projects leave them off.
  • Switching languageEarn it.CPU-bound work, hard latency targets, or correctness-critical logic.

06 Who is still accountable

The deciding question is not developer time, and it is not benchmark numbers. It is whether a human is still on the hook for the system.

If someone gets paged at three in the morning, that person has to read and debug this code. The language should match their fluency, not the agent's. Which cuts both ways, and it is why I have not landed on a single recommendation: a team fluent in Rust gets the compiler's guarantees and a debuggable system at 3am, while a team that adopted Rust because an agent writes it well gets the guarantees and an unreadable pager alert.

And if the honest answer is that nobody reads it at all, then stronger static guarantees matter more, and the strict language wins that argument outright. I would still be much more worried about the arrangement than about the language it is written in.

Which is the same conclusion as the containment piece, arrived at from the other direction. Put the guarantee somewhere that does not depend on your attention, because your attention is the thing that ran out.