Iden Tumuhirwe
All writing

July 15, 2026 · 14 min read

Bun rewrote itself in 11 days. Everyone argued about the wrong thing.

Opinion · Bun · Zig · Rust · AI · LLMs

I did the thing you're supposed to do before you have an opinion. I watched a few of the videos, got annoyed at how confident everyone was, and went and read the actual posts. Bun's own writeup on moving to Rust. Andrew Kelley's response, which is the Zig creator basically processing a breakup in public. Dayvster arguing that Bun leaving is the best thing that could happen to Zig. A long Ziggit thread where the community reverse-engineers what Bun was actually doing with the language all along. Then the usual pile of secondary coverage that turned it into a spectator sport.

Here's where I landed, and it's not where the discourse is. The language was never the story. Zig versus Rust is the costume. Underneath it are two much stranger things nobody wanted to sit with, because one of them makes Bun look less heroic and the other makes the whole industry look less safe than the headline suggested.

The version everyone repeated

For anyone who missed it: Bun, the JavaScript runtime, was written in Zig. In May the team rewrote essentially the whole thing into Rust and shipped it as Bun 1.4. The rewrite took about eleven days. It was done mostly by 64 parallel instances of Claude running a mechanical, file-by-file translation, at something like a hundred and sixty-five thousand dollars of API spend. Half a million lines of Zig in, roughly double that in Rust out, six thousand-plus commits.

The stated reason was memory safety. Bun's post lists the bugs that had been haunting them: a use-after-free in node:zlib, more use-after-frees in node:http2, a double-free in the CSS parser. Their argument is that Zig gives you no automatic cleanup, you write defer by hand at every call site, and in a codebase that constantly hands memory back and forth between a garbage-collected JavaScript engine and hand-managed native code, the human failure rate on that is never going to be zero. Rust's borrow checker and Drop turn a class of those into compile errors. Compiler errors beat a style guide. That's the whole pitch, and honestly, as a pitch, it's clean.

Then Kelley responded, disputed several of the specifics, and called the result unreviewed slop. It's a shame, because both of them said true things, and the fight over the language buried the two facts that actually matter. One of them is technical. The other is that Bun had been an Anthropic company since December, and the entire rewrite ran on Anthropic's own infrastructure, which quietly changes what this rewrite even was. Hold that thought.

Zig wasn't really the thing that broke

This is the part the Ziggit thread gave me that none of the hot takes did. Bun barely used Zig the way Zig wants to be used. They leaned on vendored C, hand-rolled allocators, and their own bun.sys layer, with internal guidance to avoid the standard library: prefer bun.sys over std.posix, don't reach for std.fs. They used Zig as a nicer C with comptime, and opted out of most of the parts of the language that would have had anything to say about safety.

Sit with that for a second, because it quietly undercuts Bun's own blog. If you deliberately wrote C in Zig's clothing, hand-managing every lifetime because that's how you get the performance you're selling, then "Zig didn't protect us from memory bugs" is a bit like ripping the seatbelts out of a car and then complaining about the safety rating. Zig's answer to "we have no automatic cleanup" was never "there is none." It was "there's a standard library and a set of idioms you chose not to adopt." Bun's architecture was manual on purpose. The bugs followed from the manual part, not from the four letters in the language name.

So when Kelley points out that their own test suite catches bugs in a million lines of unreviewed Rust, so why couldn't it catch them in the Zig, he's making a sharper point than it first reads as. The instability was a process shape, not a language shape. And you carry process shapes with you when you move.

You can't Drop your way out of a garbage collector

Now the technical thing I haven't seen anyone say plainly, and the reason I don't fully buy the safety story even though I like Rust.

Look again at the bugs Bun put in the shop window. node:zlib. node:http2. The CSS parser feeding results back to JS. Those aren't random interior bugs. They live at the exact seam where JavaScriptCore's garbage collector owns an object's lifetime on one side and native code holds a pointer to it on the other. That seam is the single hardest place in the entire codebase, and it's precisely where Rust's ownership model has the least authority. Rust's whole guarantee comes from the compiler proving at build time when each value dies. A garbage collector decides that at runtime, off global state no compiler can see, so the two models just don't compose. The borrow checker reasons about lifetimes it can see; a GC's lifetimes are, by definition, the ones it can't. You cannot express "this is alive until a garbage collector three layers away decides otherwise" in a Drop impl. That's what unsafe and raw pointers are for.

So a faithful, mechanical translation of Bun does exactly what you'd predict. Everywhere the Zig did something the borrow checker would reject, which is everywhere the interesting bugs were, the translation reaches for unsafe. The numbers bear this out brutally.

unsafe blocks, comparable-size Rust projects

uv    ▏ 73
Bun   ████████████████████████████████████████ 13,000+
                                        ~178x more

The merged rewrite ships with something north of thirteen thousand unsafe blocks across more than seven hundred files. For scale, uv, a Rust project of comparable size from the same corner of the ecosystem, has seventy-three. That's not a rounding difference. It's a different category of object. Reports out of the community had the codebase failing basic miri checks and permitting undefined behaviour from safe Rust, which is the specific thing safe Rust exists to make impossible.

So did Bun eliminate its worst class of bug, or did it move that class into thirteen thousand rooms where the borrow checker was politely asked to stand down? The honest read is the second one. They didn't kill the use-after-free. They changed its syntax highlighting. The test suite passes at 99.8%, which tells you the thing behaves identically at the public interface. Of course it does, it's a transliteration. But "behaves the same" and "is now safe" are different claims, and the blog post is selling the first while implying the second.

That's not nothing, to be clear. A fresh Rust codebase with RAII as the default, where the unsafe is now concentrated and greppable instead of diffuse across every manual defer, is a better starting position than where they were. But it's a better starting position, not a finished safety story. The safety they marketed arrives later, if someone does the slow work of shrinking those thirteen thousand blocks toward seventy-three. Nobody's tweeting when that work happens, because it isn't a launch.

This was a demo, and the demo worked

Here's the thought I asked you to hold, and it's the fact the language argument kept everyone from staring at. The rewrite was done on Anthropic's own infrastructure, by essentially one engineer directing Anthropic's own model, spending Anthropic's own API credits. A hundred and sixty-five grand that is, functionally, the company paying itself. And it shipped with a blog post about how a job that would've taken three engineers a year got done in eleven days.

Read in that light, the memory-safety framing is the stated reason and the migration itself is the product. The most valuable output of this project was never Bun 1.4. It was the sentence "we mechanically rewrote a real, load-bearing, half-million-line production runtime across languages in under two weeks, and it passed the tests." That sentence sells a lot of tokens. Every engineering leader who's ever been quoted a year and three headcount to escape a language decision just watched a live demo of the alternative. That's the asset. Bun is the surface it ran on.

I'm not saying that cynically, exactly. I'm saying if you evaluate the rewrite as "was this the best engineering choice for Bun" you'll argue forever, and if you evaluate it as "was this a stunning demo of a capability Anthropic wants the market to believe in," it's an unambiguous success and the unsafe blocks are a footnote you clean up after the story's already been told.

And the values collision underneath makes it almost too neat. Zig formally banned LLM-authored contributions in April. Bun had been running a private Zig fork it couldn't even upstream, because the changes were AI-generated and unwelcome. So this was never really Zig versus Rust. It was a project that had gone all-in on machine-written code, owned by the company that sells the machine, parting ways with a language community that had just drawn a hard line against exactly that. The runtime switched syntax. The actual disagreement was about who, or what, is allowed to hold the pen.

Where Kelley is right, and where he handed it away

Kelley is technically right on most of the specifics, and the fuzzing point is the one that stings. Cross-language LTO was never a Zig limitation, and the binary-size and comptime wins were Zig-side cleanup that could've shipped years ago. Fine, granted. But then: the post implies diligent fuzzing, while the Zig team's account is that Bun told them, on calls, they weren't fuzzing much of anything. If that's accurate it's the whole ballgame. It means the crashes got billed to a language when the invoice belonged to a practice, which is the same conclusion the codebase's own history points at.

Where he lost me, and lost the argument in public, is the delivery. Calling it unreviewed slop, narrating his resentment, describing the collaboration as a shit show: all of that let a correct technical case get waved away as sour grapes from a guy watching his most famous adopter walk out. To his credit, later in the same post he catches himself doing it and names his own resentment, which is more honesty than most people manage in public. But by then the framing's set. When you're right on the merits, tone is the only thing that can cost you, and it did. Bun got to be the calm shipper and Zig got to be the bitter ex, and that ordering had nothing to do with who was correct.

Dayvster's take is the sharpest of the bunch and the one I most enjoyed disagreeing with. His good line isn't "good riddance," it's that Anthropic just paid for the cleanest before-and-after the Zig community could ever get: same team, same architecture, same product, one variable swapped from Zig to Rust. If Bun suddenly turns rock-solid, Zig was holding it back. If the flakiness follows it into Rust, that's telling. He calls it a crash test a multi-billion-dollar company footed the bill for, and he lays out a scenario table where Anthropic comes out ahead in every outcome.

I love the framing, and I don't think the experiment has a clean arm. A natural experiment only tells you something if you actually changed one variable, and a mechanical, file-by-file transliteration didn't. It preserved the exact ownership patterns that produced the bugs: same manual lifetimes, same GC seam, now wearing unsafe hats. So if instability follows Bun into Rust, Dayvster reads it as "see, Zig was never the problem," and the Rust crowd reads it as "that's not real Rust, look at the unsafe," and both are right, which means the crash test proves nothing to anyone who didn't already have a side. You can't run a clean before-and-after when the "after" is the "before" in a different font.

And that scenario table where Anthropic wins every row? He noticed the tell and didn't pull the thread. If your sponsor profits under every branch, the branches aren't the point. The migration already paid off for the party that mattered on the day it shipped. Which is why I don't buy his "high-stakes gamble for Bun" as the main event. The gamble is real for Bun the runtime, but the house already cashed out.

Where he's plainly right: the culture mismatch was real. A project in permanent hype mode was always a strange flagship for a language whose whole personality is doing things slowly and on purpose. I'd just add the part he waves off. Zig also lost its loudest existing proof that it scales to half a million lines in anger, and "the language the AI-slop migration fled" is now a line in its story whether it earned it or not. Losing a bad fit is a relief. It isn't a win.

The switching cost died. The correctness cost didn't.

If you want one thing to take from all of this, here it is. For forty years, "which language do we build on" was close to irreversible. The cost of changing your mind was measured in years and careers, and that irreversibility is why language choice was tribal, why the arguments got religious. You were picking a marriage, so you defended it like one.

That cost just fell off a cliff. Not to zero, but far enough that a company can now A/B test a language substrate on a real product in a fortnight. That's genuinely new, and genuinely destabilizing to how we've argued about this stuff my whole career.

And it points somewhere bigger than one runtime. If half a million lines can be re-expressed in another language in eleven days for the price of a used car, then the codebase stops being the durable thing you build carefully and protect. It becomes a substrate: a disposable rendering of some higher-level intent, re-derivable on demand into whatever language, whatever style, whatever's in fashion that quarter. The Zig was one printout. The Rust is another printout of the same thing. Bun's own porting guide, telling the agents to translate faithfully file by file, is basically an admission that the source of truth had already moved up a level, out of the code and into the description of the code. That's the actual 2026 shift hiding under this whole drama. We're walking into an era of throwaway codebases, where a million lines is regenerable output instead of accumulated capital. And I get why that's intoxicating: capital is heavy, output is cheap.

But here's the part the eleven-days headline buries. Collapsing the switching cost is not the same as collapsing the correctness cost. Translation got cheap. Being right did not. The borrow checker still can't see through a garbage collector. The thirteen thousand unsafe blocks are still there. The miri failures don't care that the migration was fast. All the cheap part did was move the expensive part downstream, into production, six months out, quietly, long after the blog post's traffic died and everyone moved on to the next launch.

And that's the bit that lands differently from where I sit. The whole world does not get to spend a hundred and sixty-five thousand dollars to find out whether it likes a language better. Most of us are shipping against real constraints with a fraction of that and no acquisition to underwrite the experiment. So the lesson I'm taking isn't "rewrite everything with agents." It's the opposite: watch this one carefully, and count the cost at month six instead of day eleven. The demo already worked. Whether the software is safe is a question that, by its own design, nobody can answer until the part that skips the thinking sends its bill.

I hope it's fine. I use Bun. But I've spent enough of my career being handed a number instead of an explanation to be suspicious of any story where the hard part got done suspiciously fast and the celebration came first.

Sources

Everything above is me arguing with other people's work. Go read the originals, they're all worth it:

The 13k unsafe blocks and miri stuff came from community analysis in byteiota's writeup and this HN thread. The "unreviewed slop" quote and the wider reaction are via The Register. Reporting is theirs. Bad takes are mine.