Bun · Rust codebase audit · May 21, 2026 · AI generated

Bun's unreleased Rust port has
13,365 unsafe blocks.
Most can be removed.

Bun's Rust port has not shipped in a released build yet. The Bun you install today still runs the original Zig implementation. This audit is the pre-release pass over the port.

Every unsafe counted with one ripgrep command, then sorted by what removing it takes — command and raw data at the bottom, re-run it yourself. The bar groups all 13,365 by where each could go.

Where it comes from

Every site by root cause. Three — performance, the Zig port, the FFI boundary — cover two-thirds.

Compared to other Rust runtimes

Unsafe per 1,000 lines of Rust, measured the same way in each project. Density tracks how close the code sits to a C boundary: a crate that only binds a C++ engine is densest, a runtime that writes its own engine in Rust is sparsest. Bun keeps its bindings and runtime in one workspace; Deno splits the binding into rusty_v8 and writes much of its runtime in TypeScript. Draw your own conclusions.

Counted with the measurement command from the methodology section, vendored C/C++ excluded, May 21 2026, commits pinned per row. Lines are Rust source only.

Where it lives

Area = unsafe sites. Greener = more of them replaceable. Click a tile for its breakdown.

fewer sound destinationsmore area ∝ sites · Σ =
Tap a subsystem tile above to drill in.

Origin → pattern → outcome

Root cause → pattern → outcome, for every site. Click a node to trace it. The Zig-port and callback unsafe mostly ends up green (replaceable); the FFI unsafe mostly ends up blue (stays, behind a wrapper).

← scroll →
Click an origin, pattern, or destination to trace where its sites flow.

What the count is made of

Six facts about the 13,365 that the raw number doesn't carry.

15 patterns, five outcomes

A site counts as fixed only if safe code can't cause undefined behavior in a release build. Debug assertions don't qualify; neither do wrappers that just hide the invariant. Everything else stays unsafe — the blue and gray. The four largest patterns were measured per site; the rest are estimates over verified counts. Expand a row for examples and the fix.

Σ = 13,365 · sorted by size · the bar is each pattern's outcome splitCollapse all

Three questions, answered per site

Three questions determine most of the ledger, and each one means reading the site. Two classifiers answered independently; an adjudicator settled every disagreement against the code. Where agreement was low, trust the number less. Each pass samples by its own membership rule — the first read 3,531 sites, of which 2,750 are the two largest patterns in the ledger — raw *mut Self state machines and &self→&mut casts. The measured ratios apply to the ledger counts, and the steps consume the ledger cells.

Soundness first, then the count

Eight steps. The first fixes the safe functions that are wrong today — no change to the count. The rest move ~9,300 sites to safe code and leave ~4,000 unsafe. Per-pattern figures are in the ledger above.

← scroll →

How this was measured, and what to be skeptical of

A snapshot of the Rust port at commit 3eb0fda021, before it has shipped in a release — counts and line numbers move as the code does. The ground truth is one ripgrep command anyone can re-run, and everything above reconciles to it.

Measurement command:
Raw data: per-group census reports and adversarial reviews (102 + 102 files) · per-site classifications from both independent classifiers plus the adjudicated final (33 × 3 files) · the flagged safe-function list with claimed trigger sequences · the synthesis documents this page renders.