Zig vs Rust in the Real World: What a Runtime Port Actually Changes
Picture this: a familiar stack trace shows up, someone mutters “memory bug,” and the team starts debating languages like they’re debating religion. Then a headline lands: Bun is being rewritten in Rust—after years of being written in Zig. Suddenly the story isn’t about a compiler flag or a tiny refactor. It’s about how we build systems that stay correct while the world (and the business plan) keeps moving.
This is where the Zig creator Andrew Kelley’s blunt response to the Bun rewrite meets a larger, messier question: when AI money, IPO timelines, and “software is going away” narratives start influencing staffing decisions, do we still care about the actual technical work? In this post, we’ll focus on the concrete engineering side—what changes when you port a runtime, why memory safety dominates the conversation, and what you should look for when large rewrites claim they’re “the bleeding edge.”
Why this debate feels bigger than languages
Bun isn’t a toy project. It’s a JavaScript runtime (a program that runs JavaScript and related code outside the browser) with a massive scope: bundling, transpiling, module resolution, and Node-compatible APIs. In 2025, Anthropic acquired Bun as part of its broader Claude Code push. (anthropic.com) That’s the kind of corporate context that attracts speculation.
At the same time, Anthropic has been moving toward public markets: for example, reporting says it filed for an IPO in mid-2026. (techcrunch.com) When capital markets get involved, the narrative incentives shift. A convincing “future vision” can matter as much as engineering truth.
That’s the emotional environment in which Zig-vs-Rust arguments flare up—sometimes covering the actual technical issues, and sometimes turning them into spectatorship. Skepticism is healthy here. The real signal is still: Does the rewrite reduce serious classes of bugs, and does it maintain correctness under pressure?
The technical reality: Bun’s stability problem isn’t theoretical
The Bun rewrite announcement lays out a key detail: the project’s earlier stability work in Zig involved repeated crashes tied to memory lifetime mistakes—examples include use-after-free and heap out-of-bounds issues. A use-after-free is exactly what it sounds like: code keeps using a pointer (a reference to memory) after the memory was released back to the system.
In lower-level languages, these bugs aren’t bugs you “clean up later.” They are the kind that can turn into security issues, nondeterministic crashes, or data corruption that only shows up under certain timing.
So when Bun moved from Zig to Rust, the stakes weren’t “taste preference.” The stakes were: can the team reduce the number of lifetime and aliasing mistakes that slip through?
Zig’s “control” vs Rust’s “contracts”
To understand why the Rust move matters, it helps to define the key terms.
- Zig is a systems programming language (close to the hardware, with manual control over low-level behavior). It gives you power, but that power includes the opportunity to introduce lifetime mistakes.
- Rust is also a systems language, but it centers on a compile-time borrow checker.
- The borrow checker is Rust’s rule system that enforces memory safety by tracking who can access data and for how long. In plain terms: Rust tries to prevent you from having two conflicting views of the same memory, or using memory after it should be invalid.
- Rust also has
unsafeblocks. Anunsafeblock is Rust’s way of saying: “the compiler can’t guarantee memory safety here, so the programmer promises to uphold the rules.” That’s powerful, but it’s also a spotlight—any time you see a large amount ofunsafe, you know you’re leaning on human discipline rather than compiler enforcement.
The Bun team states that at the time of writing, about 4% of Bun’s Rust code sits inside unsafe. (bun.com) That doesn’t mean the code is “perfectly safe.” It means the unsafe footprint is being quantified, not hand-waved away.
What a “rewrite” really looks like for a runtime
A runtime port isn’t like rewriting a UI component. It’s closer to translating an engine: thousands of assumptions about data flow, pointer lifetimes, parsing behavior, performance characteristics, and threading behavior.
Bun’s port description focuses on process, not just language. They report rewriting over 11 days, with a large amount of parallelized automation and continuous review loops. (bun.com) They also provide hard numbers:
- the landed diff added about +1,009,272 lines
- 0 tests skipped or deleted during the 11-day window (bun.com)
- pre-merge token usage was on the order of $165,000 at API pricing (bun.com)
Those details matter because they connect the “shipping speed” claim to something measurable: the pipeline ran tests, generated code, and iterated.
The tools that make memory-safety claims believable
When a runtime team talks about “stability,” readers should look for whether the process uses specific bug-finding tools.
Bun’s Rust rewrite notes mention a stack of mechanisms: Miri, LeakSanitizer, and coverage-guided fuzzing. (bun.com) Here’s what those are.
- Miri is a Rust interpreter used in CI to find undefined behavior in Rust code. In practice, it catches certain classes of “this pointer is invalid” mistakes before they become production crashes.
- LeakSanitizer detects memory leaks by instrumenting runs to see which allocations aren’t freed.
- Fuzzing is automated test generation that bombards parsers with weird inputs. Coverage-guided fuzzing means the fuzzer tries to explore new code paths, not just generate random data.
Bun describes adding 24/7 coverage-guided fuzzing for many parsers (JavaScript, TypeScript, JSON variants, YAML, TOML, Markdown-like syntaxes, and more). (bun.com) The key idea: parsing logic is a minefield for both correctness and performance, and fuzzing is one of the most realistic ways to stress it.
If a rewrite only claims “we wrote it in Rust,” that’s not convincing. If it also reports the bug-finding tools it relied on, it’s closer to engineering.
A subtle porting trap: “same shape” code can behave differently
One of the most confusing parts of porting is that code can look identical but be semantically different.
Bun calls out a specific example around debug-only assertions. Rust has debug_assert!, which is a macro that disappears in release builds, removing not just the check but also any side effects inside the assertion expression. (bun.com)
In Zig, assert is a function-like construct whose argument expression behavior differs from Rust’s macro approach.
The technical lesson isn’t “Rust is tricky.” The lesson is: a large port needs semantic awareness, not text substitution.
Here’s a simplified Rust illustration of the trap concept:
fn insert_stale() -> usize {
// imagine this mutates state
42
}
fn demo(debug_mode: bool) {
if debug_mode {
debug_assert!(insert_stale() == 42);
} else {
// In release builds, the debug_assert! code is removed,
// so insert_stale() never runs.
}
}
The danger in a runtime rewrite is the same: a side effect hidden inside a check might accidentally stop happening in release mode. That can produce “works in debug, fails in production” bugs.
Where the human story enters (and why it still matters)
The Burr-in-the-sauce question is whether all this process was primarily technical, or whether it’s mostly theater.
Andrew Kelley’s response frames the situation as a clash of engineering values and quality culture, criticizing Bun’s practices and communication while also acknowledging the broader context. (andrewkelley.me) The argument is not “Rust is bad” or “Zig is good.” It’s about incentives, review habits, and how much engineering discipline a large rewrite can maintain.
That’s why the debate can’t be dismissed as fandom. When companies have strong financial momentum—especially toward public markets—teams can be pressured into shipping narratives as much as software.
So the technical question becomes: What should a responsible team demand before trusting a massive automated rewrite?
For runtime engineering, the answer is usually a bundle:
- evidence that tests run continuously and aren’t papered over (Bun reports no skipped/deleted tests in the rewrite window) (bun.com)
- evidence that unsafe usage is measured and contained (
unsafearound ~4% at the time of writing) (bun.com) - evidence that deeper dynamic tools run (Miri and sanitizers) (bun.com)
- evidence that fuzzing hits the parts most likely to explode (parsers, formats, edge-case grammars) (bun.com)
The takeaway: the language choice is the headline, but the pipeline is the body
Zig vs Rust is a useful metaphor because it forces a conversation about memory safety. But the port isn’t won by language syntax alone. It’s won by engineering systems: review loops, semantic awareness, test discipline, and bug-finding tools that target the actual failure modes.
Bun’s rewrite story also highlights a sobering reality about modern software: rewrites now come with AI-generated code at scale, which increases throughput while increasing the need for explicit safety rails. The most interesting part isn’t that automation wrote code quickly. The most interesting part is whether the team built guardrails strong enough to keep correctness intact.
The spade here is simple. When a runtime must survive real-world inputs and unpredictable timing, the winning strategy is not rhetoric—it’s measurable stability work.
And languages? They’re the tools. The pipeline is the product.
Comments (0)
No comments yet. Be the first to respond!
Leave a Comment
Your comment will be visible after review.