Bun rewrote 535,000 lines of Zig in Rust. In 11 days.
I use Bun daily. It installs this site's dependencies, runs its dev server, and builds it for production. So when the Bun team announced they had ported the entire runtime from Zig to Rust, I read everything I could find. The numbers are hard to believe, and the reasoning is more interesting than the headline.
Here's what actually happened, and what it means if Bun is in your toolchain.
The problem: two memory models in one process
Bun embeds JavaScriptCore, a garbage-collected JavaScript engine, inside a runtime written in Zig, a language with fully manual memory management. Every boundary between the two is a place where a GC-managed JavaScript value and a manually-freed Zig allocation have to agree about who owns what and for how long.
At small scale that's a discipline problem. At 535,496 lines, it became a bug factory. The team's own examples from v1.3.14 tell the story:
- a heap-use-after-free crash in
node:zlibwhen calling.reset()on a stream with an async.write()still in flight - use-after-free crashes in
node:http2when re-entrant JavaScript callbacks triggered hashmap rehashes mid-iteration
Zig has no destructors and no RAII. Cleanup means remembering an explicit defer at every call site, on every error path. Miss one and you leak; free too early and you crash. The announcement's core admission is that this class of bug kept reappearing faster than one-off fixes could kill it.
Rust's answer is structural rather than disciplinary. The Drop trait runs cleanup automatically when a value goes out of scope, on every path, including the error ones:
// Zig: cleanup is a convention you must remember
// var buf = try allocator.alloc(u8, size);
// defer allocator.free(buf); // forget this on one error path and you leak
// Rust: cleanup is a guarantee the compiler enforces
{
let buf = vec![0u8; size];
// ... use buf ...
} // Drop runs here, on success and on early return alikeOne concrete result: Bun.build() leaked about 3MB per invocation in v1.3.14. In v1.4.0, memory stabilizes at around 600MB across 2,000 consecutive builds.
How the port was actually done
This is the part everyone is arguing about. The rewrite was executed almost entirely by AI agents (Claude, via parallel agent workflows), between May 3 and May 14, 2026:
| Metric | Value |
|---|---|
| Zig files ported | 1,448 |
| Lines of Zig | 535,496 |
| Duration | 11 days |
| Peak parallel agents | ~64 (16 per worktree, 4 worktrees) |
| Commits | 6,502 |
| Compiler errors fixed after the port | ~16,000 |
| Token cost | ~$165,000 at API pricing |
The methodology matters more than the scale. Before automating anything, the team wrote a porting guide mapping Zig patterns to Rust patterns, analyzed struct field lifetimes, trial-ported three files by hand, and had the guide itself adversarially reviewed. Each crate then got one implementer agent plus two adversarial reviewer agents that saw only diffs and were instructed to hunt for bugs, with a fixer agent behind them.
The safety net was the test suite: 1,386,826 expect() calls across 60,624 tests, written in TypeScript and therefore implementation-agnostic. Zero tests were deleted or skipped, and the suite had to pass on all six supported platforms before merge. Post-merge, the team ran eleven rounds of security review and continuous coverage-guided fuzzing of the parsers.
It still wasn't flawless. Nineteen regressions shipped and were later fixed, and they're instructive because they're exactly the semantic gaps a mechanical port misses: Zig's assert keeps side effects in release builds while Rust's debug_assert! compiles them away; Zig's ReleaseFast removes bounds checks while Rust release builds keep them; odd-length UTF-16 byte handling differed at slice boundaries.
Did it get slower?
The instinct says a safety-focused rewrite costs performance. The published Linux x64 benchmarks say otherwise:
| Benchmark | Zig (v1.3.14) | Rust (v1.4.0) | Change |
|---|---|---|---|
| Bun.serve | 169.6k req/s | 177.7k req/s | +4.8% |
| next build | 13.62s | 13.03s | +4.5% |
| vite build | 1.69s | 1.65s | +2.2% |
Binaries also shrank by roughly 20% on Linux and Windows after linker and ICU changes, and startup on Linux improved by about 10%.
Two honest caveats. First, about 4% of the Rust code sits in unsafe blocks, mostly single-line pointer conversions at C and C++ library boundaries, so "rewritten in Rust" is not the same as "memory-safe everywhere." Second, these are the project's own benchmarks; independent numbers will take time.
The controversy
Not everyone is applauding. The loudest criticism comes in two flavors:
- The Zig question. Skeptics argue the team never showed that targeted fixes (stricter linting, allocation conventions, better tooling) couldn't have solved the memory bugs without abandoning the language. Zig also just lost its highest-profile production showcase, which stings independently of the technical merits.
- The AI question. A million-line diff written by agents in eleven days is, by definition, code no human has fully read. The team's answer is that verification was never "a human read it"; it was the test suite, the adversarial review agents, the fuzzers, and the security review rounds. Whether that's a sufficient standard for a runtime that millions of apps sit on is a genuinely open question, and I don't think dismissing either side is honest.
My own take: the interesting shift is that correctness argumentation moved from "trust the author" to "trust the harness." That's the same trade we already accept from compilers and optimizers. The difference is maturity; we've had fifty years to learn where compilers lie to us.
Should you upgrade?
The Rust version ships as v1.4.0, currently on the canary channel:
bun upgrade --canary
bun --versionMy plan for this site and client work:
- Dev machines: canary now. Real-world usage is exactly the feedback the project needs, and a runtime crash in dev costs me a restart, not an incident.
- CI and production builds: wait for v1.4.0 stable. The 19 fixed regressions were all in the semantic-gap category that only surfaces under real workloads. Give it a few weeks of other people's traffic first.
The v1.3.14 Zig build remains the safe pin if anything misbehaves:
{
"packageManager": "bun@1.3.14"
}Whatever you think of how it was built, a faster, smaller, leak-free Bun with an identical test surface is a good outcome for everyone who runs it. The way it was built is the part we'll still be arguing about in five years.
If you want to compare notes on canary behavior, message me; I'm collecting weird cases.