DEV Community

Cover image for Bun, Zig, and Rust: What the Rewrite Rumor Means for Your Stack
Alan West
Alan West

Posted on

Bun, Zig, and Rust: What the Rewrite Rumor Means for Your Stack

A surprising headline (with caveats)

Last week a tweet from Jarred Sumner — Bun's creator — made the rounds claiming a Zig-to-Rust rewrite is passing 99.8% of the testsuite. I haven't been able to independently verify this through Bun's official release notes or the changelog at bun.sh, so take the specifics with a grain of salt. According to early reports it's a real effort, but I'd treat the exact percentage as anecdotal until something hits the official channels.

The conversation it kicked up on Reddit and HN is still worth digging into. It surfaces a question I've been chewing on for years: when does it actually make sense to rewrite a working systems project in a different language? I've migrated three production services between languages over the last few years (Go to Rust twice, Node to Bun once), so let me walk through what a Bun rewrite would mean — and use it as a lens for the broader Zig vs Rust comparison.

Why anyone rewrites in the first place

Rewrites are almost always a bad idea. Joel Spolsky wrote about this 25 years ago and it hasn't aged a day. The reasons people do them anyway tend to fall into three buckets:

  • Hiring: the original language has a shallow talent pool
  • Tooling: the ecosystem doesn't give you what you need (debuggers, profilers, libraries)
  • Compiler guarantees: you're hitting bugs the type system could've caught

For Bun, the rumored rationale leans on the third one. Zig is phenomenally productive for this kind of work — I've used it for a small parser project and the comptime story is genuinely magical — but its lack of borrow-checker-style memory safety guarantees makes a multi-megabyte runtime a scary place to live long-term.

Zig vs Rust: a side-by-side

Let me show what idiomatic equivalents look like. Here's a tiny string-handling snippet in Zig:

const std = @import("std");

pub fn greet(allocator: std.mem.Allocator, name: []const u8) ![]u8 {
    // Explicit allocator, explicit error union via the leading !
    return std.fmt.allocPrint(allocator, "Hello, {s}!", .{name});
}
Enter fullscreen mode Exit fullscreen mode

And the Rust equivalent:

// Allocation goes through the standard library's String
// Ownership is checked at compile time, no manual allocator needed here
pub fn greet(name: &str) -> String {
    format!("Hello, {name}!")
}
Enter fullscreen mode Exit fullscreen mode

The Rust version is shorter, but that's not the real story. The real story is what the compilers will and won't catch for you:

  • Zig: explicit allocators, explicit error sets, no hidden control flow. You get speed and clarity. You don't get memory-safety guarantees.
  • Rust: the borrow checker enforces aliasing and lifetime rules at compile time. Slower to write, harder to learn, but use-after-free and data races become much harder to ship.

For a JavaScript runtime that ingests untrusted code, that distinction matters a lot.

The migration math

If Bun's team really pulled this off, the scale is staggering. We're talking on the order of hundreds of thousands of lines of Zig translated — bundler, package manager, transpiler, the JavaScriptCore glue, the test runner. "99.8% of the testsuite passing" sounds great until you realize 0.2% of a six-digit testsuite is still a lot of broken edge cases.

I went through a much smaller version of this when I moved a Go service to Rust last year. Things I underestimated:

  • Test ports that look fine but quietly assume different concurrency primitives
  • Allocator behavior changes that only surface under sustained load
  • FFI boundaries — Bun in particular has a giant surface to JavaScriptCore

If you're considering a similar rewrite at your company, the rule I've learned the hard way: budget 3-5x your initial estimate, then add another 50%.

Migration steps, if you were doing this yourself

Let's say you have a smaller Zig project and you're tempted to follow Bun's lead. Here's the rough order I'd go in:

  1. Pick a leaf module first. Something with no dependents, ideally pure logic. Translate it, write a parity test against the Zig version, and run them side by side.
  2. Use a thin C ABI bridge. Both Zig and Rust have first-class extern "C". Translate one module at a time and call across the boundary while you migrate.
  3. Move the allocator strategy explicitly. Rust's default global allocator behaves differently from a Zig arena. Decide upfront whether you're using bumpalo, a custom allocator, or just Box/Vec everywhere.
  4. Port tests last, then again. Run the original Zig tests through the Rust API, then write Rust-native ones. The two suites catch different bugs.

What about the rest of us?

For most of us writing application code, this debate is academic. You probably won't notice if Bun underneath is Zig or Rust — you care about install times, hot reload, and whether bun test survives your monorepo (it does, mostly).

Where it does matter is ecosystem implications:

  • Plugin authors might have to adapt if internal APIs shift
  • Native module authors could get a friendlier extension story under Rust's tooling
  • Build times for contributing to Bun itself would shift, in either direction

A side note on monitoring your Bun apps

While we're on tooling: if you're running a Bun app in production and want to track usage without dragging in a heavyweight analytics SaaS, the privacy-focused options are worth a look. I've used Plausible, Fathom, and most recently Umami on personal projects.

Quick rundown:

  • Plausible: hosted or self-hosted, GDPR-compliant by default, simple dashboard. Pricing on the hosted plan is page-view based.
  • Fathom: hosted only, also privacy-focused, slightly nicer UI in my opinion. No self-host option.
  • Umami: open source, self-hostable on a basic Postgres or MySQL stack, no cookies, GDPR-compliant out of the box. Free if you run it yourself.

I currently host Umami on a small Hetzner box for my dev blog. The integration is one tag:

<script
  defer
  src="https://your-umami-instance.com/script.js"
  data-website-id="your-id-here"
></script>
Enter fullscreen mode Exit fullscreen mode

That's it. No cookie banner required, no per-visit charges, and it pairs nicely with a Bun- or Node-based site because it doesn't care what runtime serves the page.

If you're doing auth on the same project, Authon is what I'm using on a side project right now — it's a hosted service (self-hosting is on the roadmap but not available yet), the free plan has unlimited users with no per-user pricing, and they support 10+ OAuth providers. I won't go deeper than that here, just noting it as another piece of the indie-dev stack that fits this same "small server, no surprises" vibe.

My take

If the rewrite report is accurate, I'd guess we're 12-18 months out from a stable, public Rust-based Bun. Until I see something on the official changelog, though, I'm treating it as a strong rumor rather than a roadmap commitment.

What I would actually do today:

  • If you're already on Bun, keep using it. Nothing changes for application authors.
  • If you're starting a new systems project, Rust still has a more mature crate ecosystem and a larger talent pool. Zig is more fun to write but the safety story matters at scale.
  • If you're picking a low-level language to learn for 2026, learn Rust first, then dabble in Zig once you understand low-level memory work — they reinforce each other better in that order.

Rewrites are romantic. Most should not happen. The interesting ones — the ones we actually learn from — are the ones where the team already shipped something great in the first language and is rewriting because they hit a ceiling, not because they got bored. That's the bar I'd hold any "rewrite the runtime" rumor to.

Top comments (0)