Lessons From Bun's AI-Driven Zig-to-Rust Port: Verification Is the Real Work
What Bun's agent-assisted move from Zig to Rust teaches about test suites as contracts, porting guides, unsafe code, and why verification now limits AI coding.
This article is published in English.
Sources: Bun Rewrote Its Runtime in Rust With AI. The Real Story Is Bigger Than the Code
Most engineering teams treat a language rewrite of a large production system as something to avoid for as long as possible. Bun, the JavaScript and TypeScript runtime and toolkit, reportedly moved its implementation from Zig to Rust with AI coding agents doing most of the translation, and the headline numbers are striking. The more useful story, though, is not how much code the agents produced but how the surrounding process decided which of that code could be trusted. This piece walks through that process and turns it into practices you can apply to your own AI-assisted refactors and migrations.
Why rewrites are normally avoided
A language rewrite carries a familiar list of risks. It can introduce subtle regressions that nobody notices for months, shift performance in unexpected directions, break a platform that only a handful of users run, and swallow a large share of a team's capacity. In the worst case the team ends up maintaining the old and the new implementation side by side, paying twice for every bug fix.
That is why the Bun migration draws attention. At the time of writing, the project's repository describes Bun as a Rust-based JavaScript runtime built on JavaScriptCore, and Anthropic announced that it was acquiring Bun in December 2025. Public reporting describes the port as roughly 535,000 lines of Zig translated over about 11 days, with agents handling much of the implementation work inside a tightly defined process.
It is tempting to read this as one more "AI wrote a mountain of code" headline. The better question is a different one: can an AI system carry out a very large change when humans build a strong mechanism for checking whether the result is correct? That question matters far more for how software will be built over the next few years.
A port, not a redesign
Bun was originally written largely in Zig, a systems language valued for low-level control and predictable performance. The migration was not framed as a fresh start. Its goal was deliberately narrow: keep the existing architecture and observable behavior, and change only the language the implementation is written in.
That framing is the key design decision. A conventional rewrite invites engineers to redesign as they go, swapping data structures, reshuffling modules, adding abstractions and adjusting APIs. Each of those choices is a new source of risk and a new thing to review. By ruling most of them out, the Bun team turned an open-ended creative task into a mechanical one with an unambiguous reference point: the current implementation and the tests that describe it.
The line count sounds like the hard part, but it is not. Producing syntactically valid Rust is comparatively easy. Guaranteeing that the Rust behaves exactly like the Zig it replaces is where all the difficulty lives.
The test suite as a behavioral contract
Picture an agent receiving nothing more than "rewrite this codebase in Rust." The instruction leaves every important decision open. Which parts may change and which must stay identical? Which shortcuts are acceptable? Which memory behaviors are load-bearing? And, above all, how would anyone know the output is right?
Bun already had the answer to the last question: a large, mature test suite. Once the tests are treated as a contract, the agent is no longer inventing an implementation. It is reproducing behavior that is already written down in executable form, and every file it produces either satisfies that contract or does not.
What tests can and cannot promise
A test suite is not a proof of correctness. It only checks the behaviors someone thought to encode, and a translated file can pass every test while still differing in a path nobody exercised. What tests do provide is a feedback loop that is far stronger than asking a model to judge its own output. Without them, a generated rewrite can look entirely plausible while quietly changing semantics. With them, each generated change has to earn its place.
The reported Bun workflow layered several gates on top of each other: compilation, the test suite, checks across supported platforms, code review and further validation. The model generated candidates; the engineering process decided which ones survived. That division of labor is the core idea to take away.
Why the porting guide mattered as much as the model
Before agents touched thousands of files, the project reportedly wrote a detailed porting guide describing how Zig constructs should be expressed in Rust. It covered type mappings, naming conventions, recurring translation patterns, how dependencies should be handled and explicit limits on what the agents were allowed to do.
The effect is to replace a vague request ("write this file in Rust") with a constrained one: convert the file using the agreed mappings, keep its observable behavior identical and leave the architecture alone. Agents perform much better in that kind of environment because the space of acceptable answers is small and well defined.
A guide also makes review tractable. Instead of the fuzzy question "does this look good?", a reviewer can ask two concrete ones:
- Does this Rust code behave the same way as the Zig it replaces?
- Did the agent follow the translation rules the project agreed on?
Both questions have answers that can be checked, which is exactly what a review process needs when the volume of changes is enormous.
What a useful porting guide contains
If you plan a similar migration, even at a much smaller scale, a guide of this kind typically spells out:
- how each source type, container and error mechanism maps to the target language
- naming rules, so generated code is consistent across files and agents
- which dependencies may be introduced and which are forbidden
- patterns the agent must not use, even if they would make a file compile
- what counts as "done" for a single file or module
The broader lesson applies well beyond Bun: the instruction system around an agent can influence the quality of the result nearly as much as the choice of model. Teams that already maintain agent instruction files will recognize the pattern; our guide to writing an effective CLAUDE.md file covers the same principle for everyday work.
Faithful Rust is not always idiomatic Rust
Anyone expecting the ported code to read like a from-scratch Rust design will be disappointed, and that is by intention. A faithful port should look like the thing it was ported from.
This produces a visible trade-off. Zig can express certain low-level operations tersely, while Rust tends to be explicit about types, lifetimes, ownership and the boundaries of unsafe code. A compact Zig function can therefore turn into noticeably longer Rust. The extra length is a consequence of optimizing for behavioral equivalence rather than elegance, not evidence that the result is broken.
It helps to keep two statements apart:
- "This is not the Rust I would have written from scratch."
- "This Rust is incorrect."
The first is a style judgment; the second is a defect. Treating them as the same thing leads to misplaced criticism of mechanical ports.
Port first, clean up second
The reported approach treated the mechanical translation and later refinement as separate phases. First make the Rust behave like Bun; then improve the Rust. Splitting the work this way means the team never has to reason about two large, interacting changes at once. If a regression appears during cleanup, it can be attributed to the cleanup, because the baseline port was already validated against the same tests.
Reading the unsafe blocks correctly
Observers who browsed the port noticed a large number of unsafe blocks and took that as a sign of poor-quality Rust. The reaction is understandable but ignores how the code got there.
Rust requires potentially dangerous operations, such as dereferencing raw pointers or calling foreign functions, to be marked explicitly with unsafe. Zig does not have an equivalent marker. Bun's original code is full of low-level memory manipulation, pointer work and calls into C and C++ components, so a faithful translation naturally surfaces much of that as unsafe Rust. On top of that, Bun embeds JavaScriptCore and talks to other native libraries, and the boundary between Rust and C or C++ code requires unsafe operations by its nature.
In other words, the port did not add danger; it made existing danger visible. A raw count of unsafe blocks says little about quality. More informative questions are:
- Why does this particular operation need to be unsafe?
- Is the unsafe region small enough to reason about?
- Does safe code around it prevent callers from misusing it?
- Is its behavior covered by tests?
- Could it be narrowed or removed in a later pass?
These questions also give a natural roadmap for the cleanup phase: shrink unsafe regions, wrap them in safe abstractions and document the invariants each one relies on.
When translation uncovers improvements
A literal port can still reveal opportunities the original design obscured. One reported example involved a code path where the Zig implementation crossed into C++ to fetch some information. In Rust, the relevant thread-local data could be reached more directly, skipping that intermediate call.
The conclusion is not that Rust beats Zig on speed; that generalization would not survive scrutiny. The point is narrower and more useful: moving code across a language boundary forces someone, or something, to look closely at every call path, and that scrutiny occasionally exposes work that was never necessary. Such findings deserve evaluation one case at a time, not recruitment into a language debate.
Where the engineering effort moved
The migration did not remove engineering work; it relocated it. In traditional development, a large share of human time goes into typing implementation code. In agent-assisted development, more of that time shifts toward:
- defining constraints and translation rules
- designing the workflow agents operate in
- writing and strengthening tests
- reviewing generated changes
- diagnosing failures
- deciding which files and systems agents may modify
- tightening the feedback loop after each round
The engineer becomes responsible for the system that produces and checks code rather than for every line of the code itself. For a large project, the loop looks roughly like this: specification, then generation, then compilation, then testing, then review, then correction, and around again. The model participates in several of those steps, but the design of the loop determines whether the output is worth anything. That workflow, not the line count, is the real innovation.
Why Bun was a good candidate
Not every codebase lends itself to this approach, and Bun had several advantages:
- a working implementation to serve as the reference
- a known, stable architecture
- behavior that was observable through an existing test suite
- a clearly chosen target language
- a codebase that could be split into fairly independent files and components
- a single, measurable objective: keep the program, change the language
Compare that with asking an agent to design a new product, where there is no reference implementation and "correct" is a matter of opinion. The general rule is that the more precisely the desired behavior can be specified, the more of the implementation can be automated. That does not mean agents can safely take on any large project. It means automation works best when the destination can be measured.
What the migration does not prove
It is easy to overextend a result like this, so it is worth being explicit about its limits. The Bun port does not show that AI can replace software engineers. It does not show that every codebase can be translated this way, that generated code is automatically maintainable or that Rust is always faster than Zig. And it certainly does not justify merging enormous AI-generated pull requests into ordinary product code.
If anything, it shows the opposite. The work depended on strong tests, explicit rules, validation, review, debugging and continuous human supervision. Analysis of the published workflow also points out that agents were not left to run unattended; people monitored the process and adjusted it as problems emerged. Engineering judgment did not become less important. It became the thing everything else rested on.
Verification is becoming the bottleneck
For decades, writing code has been treated as the slow step. Suppose an agent can produce 10,000 lines in the time a developer once needed for 500. The obvious reaction is to celebrate the speed-up, until the next question arrives: who is going to check those 10,000 lines?
Generating code is now cheap. Establishing that it is correct is still expensive, and the cost rises sharply for runtimes, compilers, databases, operating systems and security-sensitive code, where a small mistake can cause a large failure. That suggests growing value in everything that verifies rather than generates: test suites, observability, static analysis, fuzzing, formal methods, sandboxing and adversarial review. The teams that benefit most from coding agents may be the ones with the strongest verification systems, not necessarily the ones with access to the best model.
Practices to borrow for your own AI-assisted changes
You do not need a runtime-sized project to apply these lessons. They work just as well for migrating a service between frameworks or converting a JavaScript codebase to TypeScript.
Put tests in place before the big change
A thorough test suite is an executable specification. The more behavior you can check automatically, the more implementation work you can safely hand off. If coverage is thin, strengthening it is the first milestone of the migration, not an afterthought.
Write the rules down
Avoid prompts like "rewrite this." Specify architecture, naming, allowed and forbidden dependencies, shortcuts that are off limits, error-handling expectations and the acceptance criteria for each unit of work.
Keep generation and review separate
An agent should never be the only judge of its own output. An independent review step, whether a human, a second agent with a different brief or both, brings a fresh perspective and catches problems the generating step is blind to.
Pilot on a small, representative slice
Run the process on a handful of typical files first. Broken assumptions are cheap to fix when they affect ten files and very expensive when they have been replicated across ten thousand.
Treat generated code as untrusted until proven
Code that compiles is not code that works. Compilation is one gate among several, not evidence of correctness.
Keep people accountable for the decisions that matter
Agents can explore a huge space of possible implementations very quickly. Deciding what the system must do, and which risks are acceptable, remains a human responsibility.
Beyond the Zig versus Rust debate
It is tempting to frame the story as Zig against Rust, hand-written code against AI or humans against agents. None of those framings explains much. The Bun port is better understood as an experiment in how software gets built when producing code costs almost nothing. Once a system can translate hundreds of thousands of lines, the central question stops being "can we implement this?" and becomes "can we build a reliable process that tells us whether the implementation is right?" Those are very different engineering problems.
How the result should be judged
The initial translation is only the first phase. A mechanically ported codebase can be made more idiomatic over time: unsafe regions audited and reduced, abstractions improved, performance tuned and compatibility layers retired. Like any production system, it will ultimately be judged by whether it works for real users. The source material notes that Bun continued to ship releases after the migration; check the project's repository and changelog for its current status.
The meaningful evaluation will come from long-term signals rather than the size of the rewrite:
- whether the runtime stays stable across releases
- whether the code becomes easier to maintain
- whether performance remains competitive
- whether memory-related bugs become less frequent
- whether contributors can understand and improve the new code
Key takeaways
- The value of the Bun migration lies in the loop of rules, tests, review and correction, not in the volume of generated code.
- Framing a rewrite as a behavior-preserving port gives agents a measurable target and reviewers a concrete checklist.
- A porting guide shrinks the space of acceptable output and makes large-scale review feasible.
- Verbose or unsafe-heavy Rust in a faithful port is often the honest cost of equivalence; judge unsafe code by its boundaries, not its count.
- As generation gets cheaper, investment in verification is where teams will differentiate, and designing that feedback loop is increasingly the core of the engineering job.