devtake.dev

Bun's million-line Rust rewrite is now mainline. 99.8% of tests pass and 13,000 unsafe blocks remain.

Jarred Sumner merged the Bun-in-Rust PR on May 14, ending Zig as Bun's runtime language. Binary shrinks 3-8 MB; one analysis counted 13,000 unsafe blocks.

Soren Vanek · · 7 min read · 4 sources
GitHub Open Graph card for oven-sh/bun pull request #30412, the Rust rewrite merge.
Image: GitHub · Source

Jarred Sumner merged the PR on May 14 at 08:09 UTC. 1,009,257 lines added across 2,188 files, 30 commits, and one language swap: Bun, the JavaScript runtime that’s been Zig from day one, is now Rust on main. Sumner promised a blog post and shipped a canary instead.

This isn’t the experiment we wrote about last week. The experiment hit 99.8% test compatibility in six days and got a “very high chance this gets thrown out.” Five days later the PR landed. That’s the news. The harder question is what got merged underneath it.

What actually shipped

Bun 1.3.14, released over the weekend, is now the last version written in Zig. Anything you install with bun upgrade --canary from May 14 forward is the Rust build. Sumner posted the deltas in the PR description: the binary shrinks by 3 to 8 MB across platforms, the pre-existing test suite passes on Linux x64 glibc, macOS, and Windows, and several memory leaks the Zig version had been chasing for years got fixed in the port. The architecture, the data structures, and the choice not to bring in async Rust are unchanged. JavaScriptCore is still the engine. uWebSockets is still the HTTP layer.

The performance pitch is the calmest part of the announcement. “Benchmarks are between neutral and faster,” Sumner wrote in the PR. There’s no benchmark page yet. What there is, and what the team has been chasing in years of Zig issues, is the toolchain promise: “compiler-assisted tools for catching and preventing memory bugs, which have cost the team an enormous amount of development and debugging time over the years.” Use-after-free, double-free, and error-path memory bugs that Zig caught at runtime, if at all, now block compile.

That’s the headline. The body is messier.

The 13,000 unsafe blocks

An analysis from ByteIota counted 13,000-plus unsafe blocks across the 681,000 lines of actual Rust that came in with the merge. For comparison: uv, Astral’s Rust-based Python package manager, has 73 unsafe blocks across roughly 350,000 lines. Per-line, Bun’s port carries about 181 times the unsafe density.

That density doesn’t mean the port is broken. Bun has structural reasons to land in unsafe territory. Embedding JavaScriptCore means crossing an FFI boundary into C++ for every JS call, and FFI is by definition unsafe in Rust. Preserving the global mutable state patterns Zig allowed (and that Bun relies on internally) doesn’t map cleanly onto Rust’s ownership model without redesign. Sumner explicitly said he wasn’t doing that redesign on this pass. He wanted the same architecture, ported.

What the density does mean is that the 99.8% test-pass headline doesn’t carry the safety claim by itself. Tests run Bun’s public API. They don’t validate the memory invariants the compiler stopped checking the moment it hit an unsafe keyword. Those 13,000 blocks are 13,000 places where the team is now manually responsible for getting the rules right, and where the audit work has just begun.

A clean-up PR Sumner filed shortly after the merge got auto-closed by GitHub’s anti-AI-slop detection. He labeled it “ai slop” himself. The follow-up commits to tighten the unsafe surface are now landing one at a time.

The Zig divorce

The reason Bun didn’t ship a refactored Rust port is that it couldn’t keep shipping a Zig-based one. Last week’s piece covered the surface conflict: Zig has a strict no-AI policy, Anthropic’s tooling is core to how Bun is built, and several of Bun’s fork-only patches (including a 4x faster debug compile path) had been rejected upstream over non-determinism concerns.

The Register added one line from Sumner that reframes the divorce: “this is already the status quo; we haven’t been typing code ourselves for many months now.” He’s not saying the team experimented with Claude. He’s saying the team’s daily workflow had moved off keyboards and onto agentic loops well before this PR landed. The Zig position, in that context, isn’t a stylistic disagreement. It’s a workflow ban.

Bun could keep a Zig fork that diverged further from upstream every quarter, or it could move to a language whose maintainers don’t have an opinion about the team’s tooling. They picked the second.

How the port actually got built

Sumner’s described process is four-phase. Agents were handed the full Zig codebase; they generated Rust in parallel for different modules; compiler errors fed back through iterative correction loops; and the existing test suite acted as the regression harness. The 30 visible commits on the PR are squash artifacts. The Register cites roughly 6,755 underlying commits across the agent run, which is the cadence you’d expect from agents iterating against rustc for nine days straight.

The PR description names the trade-offs honestly. “Still some optimization work to do before this lands in non-canary version. Still some cleanup work to do (which will come in a series of follow-up PRs).” It’s a canary because it isn’t done. The pitch is that 99.8% behavioral compatibility plus a 3-8 MB smaller binary plus a memory-safe compiler is worth shipping incomplete.

Who should care

If you write Bun, you should run bun upgrade --canary on a workload you trust and file the things that break. Sumner asked for issues in the PR and the canary channel is the only way the last 0.2% of incompatibilities surface. If you depend on a Bun feature that touches the FFI boundary (bun:ffi, native addons, JavaScriptCore-specific behavior), assume it lives in the unsafe-block territory and treat the next two weeks as audit time.

If you write Zig, the divorce is the bigger story than the rewrite. Bun was, for a long time, Zig’s most prominent production user. The team’s exit doesn’t change Zig’s correctness, but it does change Zig’s pitch. Andrew Kelley has been clear that he’s not interested in AI-assisted contributions; Sumner has been clear that the contributors he can hire today are not interested in writing every line themselves. Those are not reconcilable positions.

If you write Rust, the more important precedent is the unsafe-block count. The Rust ecosystem has spent a decade arguing that “memory safe” means “compiler-checked memory safe.” A million-line agent-translated Rust codebase with 13,000 unsafe blocks is the test case for whether that promise survives the migration era. The next year of Bun bug reports will be the data.

Why you’re hearing about this now

Bun’s prior coverage cast the rewrite as an experiment. The merge inverts that framing in one calendar week. The interesting thing isn’t “Bun moved to Rust” or even “Claude wrote it.” It’s the cadence: Sumner went from “very high chance this gets thrown out” on May 9 to canary-shipping on May 14 because the test suite said it could. That’s a new tempo for runtime work, and the people who’ll have opinions about it strongest are the ones who already maintain something at this scale.

The follow-up to watch: the first non-canary release that drops the Zig build, plus whoever publishes a serious audit of the unsafe surface. Until then, the 99.8% number is the part of the story we can verify. The other 0.2%, and the 13,000 manual contracts, are the part we can’t.

The Hacker News discussion has been the closest thing to a real review while the team works through the cleanup queue, and the comment threads there are also where most of the early canary breakage is getting reported. If you’re a heavy Bun user, that’s the channel to watch for the next two weeks. Sumner’s promised blog post is the one to wait for; the underlying technical claims (the binary deltas, the leak fixes, the unsafe density) are testable now, but the design rationale isn’t fully on the record yet, and that’s the document the Zig and Rust communities will both want to read.

Share this article

Quick reference

unsafe
A Rust language keyword that opts a code block out of compile-time memory checks. Inside an `unsafe` block, the author asserts they're maintaining memory invariants the compiler can't verify.

Sources

Frequently Asked

Is the Rust version the default Bun build now?
Not yet. It merged to main on May 14, but Sumner is shipping it through `bun upgrade --canary` first. Bun 1.3.14 is the last Zig-based release. A non-canary cutover comes after the optimization and cleanup follow-up PRs land.
Does this mean Bun left Zig because Rust is faster?
No. Sumner reports neutral-to-faster benchmarks, not a speed leap. The pitch is compiler-checked memory safety and a 3-8 MB smaller binary. The break with Zig was as much about governance as it was about the language.
If 99.8% of tests pass, what's the worry?
The 13,000 `unsafe` blocks. Tests verify behavior at Bun's public API; they don't verify the memory invariants the compiler can't check inside an `unsafe` region. That's the auditing work that just started.
Did Claude write the whole port?
Sumner says four-phase agentic loops with Claude did the translation work, with human steering. He also said the team 'hasn't been typing code ourselves for many months.' Claude wrote most of the syntax; the design decisions are still his.

Mentioned in this article