# Bun을 Rust로 다시 작성하기

> Clean Markdown view of GeekNews topic #31263. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=31263](https://news.hada.io/topic?id=31263)
- GeekNews Markdown: [https://news.hada.io/topic/31263.md](https://news.hada.io/topic/31263.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-07-09T13:05:08+09:00
- Updated: 2026-07-09T13:05:08+09:00
- Original source: [bun.com](https://bun.com/blog/bun-in-rust)
- Points: 2
- Comments: 1

## Topic Body

- Zig로 시작한 Bun은 월 **2,200만 회 이상 다운로드**되는 런타임으로 커졌지만, GC 기반 JavaScript 엔진과 수동 메모리 관리가 맞물리며 반복된 안정성 문제를 Rust 전환의 계기로 삼음
- 535,496줄의 Zig 코드를 사람이 1년간 옮기는 대신, Claude Code의 **동적 워크플로우** 약 50개와 최대 64개 Claude 인스턴스를 11일 동안 병렬 실행함
- 포팅은 `PORTING.md`, `LIFETIMES.tsv`, 구현자 1명과 **적대적 리뷰어** 2명 이상, 기존 TypeScript 테스트 스위트로 검증됐고 6개 플랫폼 CI에서 100% 통과함
- Rust 전환 후 Bun v1.4.0은 v1.3.14에서 재현되는 **128개 버그**를 고치고, instrumentable 메모리 누수를 모두 수정했으며, Linux·Windows 바이너리 크기를 약 20% 줄임
- Bun v1.3.14는 마지막 Zig 버전이고 v1.4.0은 첫 Rust 버전으로 canary에 제공되며, 팀은 borrow checker, Miri, LeakSanitizer, 24/7 커버리지 기반 퍼징을 안정성 개선 도구로 사용함

---

### Zig로 시작한 Bun과 안정성 문제
- Bun은 esbuild의 JavaScript·TypeScript 트랜스파일러를 Go에서 Zig로 **라인 단위 포팅**한 프로젝트로 출발함
- 첫 Zig 코드는 2021년 4월 16일 작성됐고, Zig의 낮은 수준 제어와 성능 지향 설계가 Bun 초기 구현을 가능하게 함
- 초기 Bun은 한 명이 1년 동안 Zig로 작성했으며, 범위가 매우 넓었음
  - JavaScript, TypeScript, CSS 트랜스파일러·미니파이어·번들러
  - npm 호환 패키지 매니저
  - Jest 유사 테스트 러너
  - Node.js·TypeScript 호환 모듈 해석
  - HTTP/1.1·WebSocket 클라이언트
  - `fs`, `net`, `tls` 등 Node.js API 구현
- 현재 Bun CLI는 월 **2,200만 회 이상 다운로드**되고, Claude Code와 OpenCode가 런타임으로 사용하며, Vercel, Railway, DigitalOcean 등이 1st-party 지원을 제공함

### 반복된 메모리 안정성 버그
- Bun v1.3.14에서 수정된 버그 샘플에는 **use-after-free**, double-free, 메모리 누수, out-of-bounds 접근, race condition이 포함됨
  - `node:zlib`의 async `.write()` 중 `.reset()` 호출로 인한 heap-use-after-free
  - `node:http2`의 재진입 JS 콜백이 hashmap rehash를 유발해 내부 스트림 포인터가 무효화되는 use-after-free
  - `UDPSocket.send()`와 `sendMany()`에서 `valueOf()` 또는 `toString()` 콜백이 `ArrayBuffer`를 detach하는 문제
  - `Buffer#copy`, `Buffer#fill`에서 인자 coercion 중 `ArrayBuffer` detach 또는 resize로 인한 crash와 out-of-bounds read
  - `crypto.scrypt`, `tlsSocket.setSession()`, `fs.watch()` 관련 메모리 누수
  - CSS parser의 vendor prefix와 multi-layer background 처리 중 double-free
  - `BroadcastChannel` 또는 `MessagePort`의 동시 접근 중 `MessageEvent` race condition crash
- 기존에도 안정성 강화를 위해 여러 장치를 사용함
  - Zig compiler에 Address Sanitizer 지원을 패치하고 모든 커밋에서 ASAN 테스트 스위트를 실행함
  - Windows에는 Zig safety-checked ReleaseSafe 빌드를 배포함
  - [Fuzzilli](https://github.com/googleprojectzero/fuzzilli)로 Bun 런타임 API를 24/7 퍼징함
  - end-to-end 메모리 누수 테스트를 다수 운영함
- Zig 자체가 문제라는 입장은 아니며, GC 값과 수동 관리 메모리를 함께 다루는 요구가 안정성 문제의 주요 원천이었음

### Rust를 선택한 이유
- JavaScript는 GC 언어이고 JavaScriptCore와 V8 같은 엔진은 예외 처리와 GC에 엄격한 규칙을 가짐
- Zig는 C처럼 메모리를 자동 관리하지 않으며, 생성자·소멸자가 없고 cleanup은 대부분 각 call site에서 `defer`로 명시해야 함
- Bun에서는 GC 값과 수동 관리 값의 lifetime을 올바르게 처리하는 일이 안정성 문제의 큰 원천이었음
  - 할당된 바이트가 어디서 해제되는지 확인해야 함
  - 한 번만 해제되는지 보장해야 함
  - JavaScript 예외 처리를 올바르게 확인해야 함
  - GC 포인터가 conservative stack scanner에 보이는지 확인해야 함
- Zig의 cleanup 방식은 명시적인 `defer`, `errdefer`이고, C++은 destructor와 move, Rust는 `Drop`을 사용함
- Bun의 기존 Zig 코드에서는 arena lifetime, reference counting, 세심한 리뷰가 혼합돼 있었음
- 스타일 가이드와 코드 리뷰로 ownership 규칙을 강제할 수도 있지만, Rust의 안전한 코드에서는 use-after-free, double-free, error path의 누락된 free가 **컴파일러 오류**가 됨
- Bun 코드의 약 20%는 C++이고 여러 C/C++ 라이브러리를 내장함
  - JavaScriptCore
  - uWebSockets와 usockets
  - lshpack과 lsquic
  - BoringSSL
  - SQLite
- C++도 선택지가 될 수 있었지만, 여전히 스타일 가이드와 코드 리뷰에 의존하고 ASAN이 있어도 메모리 손상과 누수가 발생할 수 있음

### 재작성 전략: 한 번에, 기계적으로
- 기존 Bun Zig 코드는 주석 제외 **535,496줄**이었고, 전통적인 재작성은 작은 엔지니어 팀이 1년가량 걸릴 작업으로 평가됨
- 버그 수정, 보안 수정, 기능 개발을 1년 동안 멈출 수 없었기 때문에 사용자 동작 변경을 최소화하는 **기계적 포팅**이 가장 낮은 위험 접근법으로 선택됨
- Bun의 테스트 스위트는 TypeScript로 작성돼 런타임 구현 언어에 의존하지 않았음
- 증분 재작성은 임시 코드를 만들고 나중에 삭제되길 기대해야 하므로 단기·중기적으로 고통스럽다고 판단해 전체를 한 번에 옮김
- Rust 코드는 Zig 코드를 transpile한 것처럼 보이도록 작성하고, Bun v1.4 이후 점진적으로 `unsafe`를 줄이며 idiomatic Rust로 리팩터링하는 방향을 택함

### Claude Code 동적 워크플로우
- Rust 재작성에는 Claude Code에서 약 **50개 동적 워크플로우**가 11일 동안 계속 실행됨
- 워크플로우는 포팅 가이드 작성부터 파일 변환, 컴파일 오류 수정, subcommand 복구, 전체 테스트 통과, 대규모 cleanup까지 이어짐
  - Zig 패턴과 타입을 Rust 패턴과 타입으로 매핑하는 포팅 가이드 생성
  - 모든 `.zig` 파일을 `PORTING.md`와 `LIFETIMES.tsv`에 맞춰 `.rs` 파일로 기계적 포팅
  - crate별 컴파일러 오류 수정
  - `bun test`, `bun build` 같은 subcommand 동작 복구
  - 전체 테스트 스위트 통과
  - 대규모 리팩터링과 cleanup
- 대부분의 기간 동안 사람이 워크플로우 출력을 읽고 문제와 버그를 확인하며, Claude가 루프를 수정하도록 프롬프트를 조정함
- 사전 작업으로 Claude와 약 3시간 동안 Zig 코드베이스의 패턴을 Rust로 매핑하는 방식을 논의했고, 이 결과가 `PORTING.md`로 직렬화됨
- 수동 메모리 관리 코드에 Rust lifetime을 추가하기 위해 모든 struct field의 lifetime을 분석하는 워크플로우를 실행함
  - 복잡한 lifetime을 가진 field를 찾음
  - lifetime을 제안함
  - 적대적 리뷰 agent 2개가 검토함
  - 피드백을 반영해 `LIFETIMES.tsv`로 저장함

### 적대적 리뷰 방식
- 각 구현 Claude와 별도 context window에 **적대적 리뷰어 Claude**를 두고, 리뷰어는 diff만 받은 상태에서 코드가 틀렸다고 가정하고 버그를 찾도록 지시받음
- 기본 구조는 구현자 1명, 적대적 리뷰어 2명 이상, fixer 1명으로 구성됨
- 리뷰어가 실제로 잡은 버그는 모두 컴파일은 통과했지만 동작상 문제가 있었음
  - `uv_close`가 비동기인데 `Box&lt;uv::Pipe&gt;`가 match arm 끝에서 drop되어 libuv가 freed memory를 들고 있게 되는 use-after-free와 double-free
  - 음수 비정수 file time에서 `trunc()`를 쓰면 음수 `nsec`가 생기는 timespec 오류
  - `unwrap_or`가 인자를 eager 평가해 `color-mix()` percentage 생략 케이스에서 panic하는 오류
- 사람 리뷰와 마찬가지로 작성자와 리뷰어 context를 분리해, 구현자가 merge를 원해 생길 수 있는 편향을 줄임

### 대규모 포팅 실행과 병렬화
- 전체 1,448개 `.zig` 파일을 옮기기 전에 먼저 3개 파일로 절차를 검증함
  - 구현자 1명이 `.rs` 파일을 작성함
  - 리뷰어 2명이 `.zig`와 동작이 일치하고 `PORTING.md`, `LIFETIMES.tsv`를 따르는지 확인함
  - fixer 1명이 제안을 적용함
- 전체 파일 포팅 초기에 여러 Claude가 `git stash`, `git stash pop`, `git reset HEAD --hard`를 실행하며 서로 충돌함
- 이후 워크플로우에 `git stash`, `git reset`, 특정 파일 커밋이 아닌 `git` 명령, `cargo` 같은 느린 명령을 금지하는 규칙을 추가함
- 최종적으로 4개 workflow shard와 4개 worktree를 사용하고, 각 shard에서 16개의 Claude가 파일을 커밋·푸시함
- 병렬화와 사전 준비 덕분에 peak에서 Claude는 분당 약 **1,300줄 코드**를 작성함
- 포트 브랜치의 merge 제외 커밋은 **6,502개**, peak hour는 695 commits였고, 최종 landed diff는 +1,009,272줄임
- EC2 인스턴스의 기본 IOPS를 늘리지 않아 느린 `grep` 하나로 디스크 읽기·쓰기가 몇 분간 멈추는 문제도 있었음

### 컴파일 오류와 crate 분리
- 모든 코드를 작성한 뒤 Claude 워크플로우가 컴파일러 오류를 수정함
- Zig 코드베이스는 사실상 하나의 compilation unit이었고, Rust 코드는 더 빠른 컴파일을 위해 약 100개 crate로 나누려 했음
- 가장 까다로운 오류 범주는 **순환 의존성**이었음
  - Rust 재작성 직전의 crate 분리 PR만으로는 충분하지 않았음
  - 별도 워크플로우가 순환 의존성이 있는 코드를 어디에 둘지 분류하고 기록함
  - 또 다른 워크플로우가 해당 리팩터링을 수행함
- 순환 의존성 해결 후 약 **16,000개 컴파일러 오류**가 드러남
- 이 오류들은 crate별로 병렬 처리됨
  - 각 crate에서 `cargo check`를 실행함
  - 출력을 파일별로 묶어 저장함
  - 해당 crate의 컴파일 오류를 수정함
  - 적대적 리뷰어 2명이 변경을 검토함
  - fixer 1명이 수정사항을 적용함
- Claude가 “모든 crate를 컴파일되게 하자”를 함수 stub 생성으로 해석하는 false start도 있었음
- 긴 설명 주석으로 workaround를 정당화하려는 패턴이 생기자, “문단 길이 주석이 필요하면 코드는 틀렸고 코드를 고쳐야 한다”는 리뷰 규칙이 추가됨

### 테스트 통과까지의 과정
- `cargo check`가 통과한 뒤에는 링크 오류, 시작 직후 panic, `bun --version`, `bun test &lt;file&gt;` 실행을 순서대로 해결함
- CLI subcommand별 실패 stacktrace를 파일로 저장하고, 구현자·리뷰어·fixer 루프로 고치는 워크플로우를 사용함
- 테스트 파일 워크플로우는 약 100개 랜덤 테스트 파일을 4개 worktree에 shard하고, 실패별 stacktrace와 오류를 저장해 수정함
- 테스트 스위트에는 debug build에서 timeout될 수 있는 메모리 누수 테스트와 통합 테스트가 있었음
  - `next dev`를 실행하고 hot module reloading이 100회 변경을 감지하는 테스트
  - TCP socket 최대 수를 소진하는 stress test
  - 기가바이트 단위 디스크 읽기·쓰기 테스트
  - 약 1만 개 프로세스를 spawn하는 테스트
- 격리를 위해 `systemd-run`과 cgroups로 메모리·CPU 사용량을 제한하고 pid namespace를 분리함
- 그래도 머신은 디스크 공간 부족으로 여러 번 crash함
- 첫 CI 실행 이틀 뒤 실패 테스트 파일은 972개에서 23개로 줄었고, 그로부터 하루 반 뒤 Linux가 완전히 green이 됨
- 최종적으로 6개 플랫폼의 CI 전체 테스트가 통과함
  - macOS x64
  - macOS arm64
  - Linux x64
  - Linux arm64
  - Windows x64
  - Windows arm64
- 100% 테스트 통과 후 사람이 테스트가 실제 실행되고 skip되지 않았는지 수동 확인하고 merge함
- `main`에 merge된 시점은 versioned release가 아니며, release할 만큼의 확신은 아직 아니었고 rewrite에 전념할 만큼의 확신을 얻은 상태였음

### 테스트 규모와 비용
- 11일 동안 May 3부터 May 14 merge까지 **6,778 commits**가 생성됨
- 테스트는 삭제되거나 skip되지 않음
- 플랫폼별 테스트 규모는 다음과 같음
  - Debian 13 x64: `expect()` 1,386,826회, 테스트 60,624개, 파일 4,174개
  - macOS 14 arm64: `expect()` 1,259,953회, 테스트 58,850개, 파일 4,175개
  - Windows 2019 x64: `expect()` 1,007,544회, 테스트 57,337개, 파일 4,173개
- pre-merge 작업에는 uncached input token 59억, output token 6억 9천만, cached input token read 720억이 사용됨
- API 가격 기준 비용은 약 **16만 5천 달러**임
- 사람이 직접 했다면 코드베이스 전체 context를 가진 엔지니어 3명이 약 1년 걸렸을 것으로 평가됨
- 사용 모델은 pre-release [Claude Fable 5](https://www.anthropic.com/news/claude-fable-5-mythos-5)이고, Bun은 2025년 12월 Anthropic에 인수됐다는 disclosure가 포함됨

### 보안 리뷰, 퍼징, unsafe 현황
- Rust 포트 merge 이후 [Claude Code Security](https://claude.com/product/claude-security)로 **11라운드 보안 리뷰**를 완료하고 findings를 처리함
- Bun의 모든 parser에 대해 24/7 커버리지 기반 퍼징이 추가됨
  - JavaScript
  - TypeScript
  - JSX
  - CSS
  - JSON5
  - JSONC
  - TOML
  - YAML
  - Markdown
  - INI
  - Bun Shell scripts
  - semver ranges
  - `.patch` files
  - CSS colors
- fuzzer는 찾은 버그를 Claude에 보내 재현과 수정을 포함한 PR을 제출하게 하고, 사람은 PR을 리뷰함
- 지금까지 parser 실행은 **1,000억 회**였고 약 15개 PR로 이어짐
- 작성 시점 기준 Rust 코드의 약 4%가 `unsafe` block 안에 있음
  - 약 13,000개의 `unsafe` keyword
  - 약 27,000줄 / 전체 약 780,000줄
  - `unsafe` block의 78%는 한 줄짜리이며, C++에서 온 포인터 또는 C 라이브러리 호출임
- JavaScriptCore 같은 C/C++ 라이브러리를 계속 사용하기 때문에 순수 Rust 프로젝트보다 `unsafe`는 항상 더 많을 것이라고 밝힘

### Rust 전환 후 발견된 regression
- Rust rewrite는 대규모 변경이어서 **19개의 알려진 regression**을 만들었고, 모두 수정됨
- 대부분은 두 언어에서 문법은 비슷하지만 의미가 다른 코드에서 나옴
- ## `debug_assert!` 안의 side effect
  - Zig의 `assert`는 함수라 인자가 모든 build에서 실행됨
  - Rust의 `debug_assert!`는 macro라 release build에서는 전체 표현식이 제거됨
  - `insert_stale` 호출이 release build에서 사라져 React를 사용하는 HTML route 프로젝트의 특정 HMR 케이스가 깨짐
  - 관련 이슈: [#30678](https://github.com/oven-sh/bun/issues/30678)
- ## 홀수 길이 slice
  - Bun의 Zig helper `reinterpretSlice(u16, bytes)`는 `@divTrunc`를 사용해 trailing odd byte를 무시했음
  - Rust의 `bytemuck::cast_slice`는 홀수 길이에서 panic함
  - UTF-16 BOM 뒤에 홀수 바이트가 오는 `Blob.text()`가 문자열을 반환하지 않고 process를 panic시키는 regression이 있었음
  - 수정은 `&buf[..buf.len() & !1]`로 odd byte를 다시 무시하는 방식임
  - 관련 이슈: [#31188](https://github.com/oven-sh/bun/issues/31188)
- ## Bounds checks
  - macOS와 Linux의 Zig 코드는 `ReleaseFast`로 컴파일돼 bounds check가 제거됐고, Rust release build는 bounds check를 유지함
  - Bun module resolver의 overflow block 크기가 placeholder `64`로 남아 ceiling이 840만 interned filenames에서 270,272로 낮아짐
  - port된 `ptrs[4095]` off-by-one이 실제 프로젝트에서 도달 가능해졌고, Rust는 out-of-bounds write 대신 panic함
  - 관련 이슈: [#31503](https://github.com/oven-sh/bun/issues/31503)
- ## `comptime` format strings
  - Zig의 `Output.pretty`는 `fmt`가 `comptime`이라 `&lt;r&gt;`, `&lt;d&gt;` color marker가 인자 치환 전에 ANSI escape로 변환됨
  - Rust 함수는 comptime parameter가 없어 완성된 string에서 marker를 처리했고, 인자까지 잘못 rewrite함
  - `bun update -i`에서 OSC 8 hyperlink termination과 trailing `&lt;r&gt;` marker가 충돌해 `r`이 텍스트로 출력됨
  - Rust에서는 macro `bun_core::pretty!("&lt;r&gt;{}&lt;r&gt;", hyperlink)`가 필요했음
  - 관련 이슈: [#30693](https://github.com/oven-sh/bun/issues/30693)

### 수정된 버그와 메모리 누수
- Bun v1.4.0은 v1.3.14에서 재현되는 **128개 버그**를 수정함
- 범위는 메모리 누수, crash, 잘못 색칠된 help text까지 포함됨
- Rust의 `Drop`은 값이 scope를 벗어날 때 자동으로 `drop` 함수를 호출함
- Zig에서는 각 call site에 `defer`를 추가해야 해서 cleanup 누락 또는 중복 cleanup이 발생하기 쉬웠음
- Rust의 `Drop`은 hidden control flow를 받아들이는 대신 흔한 footgun을 줄이는 선택임
- `Drop`은 error handling code의 file path 관련 메모리 누수 여러 개를 수정함
- Bun의 [LeakSanitizer](https://clang.llvm.org/docs/LeakSanitizer.html) 통합이 개선돼 모든 [native code memory allocations](https://github.com/oven-sh/bun/pull/30875)를 추적함
- instrumentable memory leak는 모두 수정됨
- ## `Bun.build()` 누수 개선
  - 기존 Bun v1.3.14에서는 in-process `Bun.build()` 호출마다 parsed source text와 AST symbol table이 build 수명보다 오래 살아남아 수 MB씩 누수됨
  - 같은 60-module 프로젝트를 한 process에서 2,000번 bundle하는 테스트에서 v1.3.14는 build마다 약 3MB를 계속 누수함
  - Bun v1.4.0에서는 메모리 사용량이 수평화됨
  - | Builds | Bun v1.3.14 | Bun v1.4.0 |
  - | --- | ---: | ---: |
  - | 500 | 1,914 MB | 526 MB |
  - | 1,000 | 3,506 MB | 586 MB |
  - | 1,500 | 5,097 MB | 608 MB |
  - | 2,000 | 6,745 MB | 609 MB |

### 바이너리 크기, 스택 사용량, 성능
- Rust rewrite 초기 변경만으로 바이너리 크기가 줄어듦
  - Windows: 3.8 MB 감소
  - macOS: 5.5 MB 감소
  - Linux: 6.8 MB 감소
- 주요 원인은 Zig 코드에서 `comptime`을 너무 많이 사용한 점이었음
- 이후 동일 코드 접기(Identical Code Folding), ICU의 unused data 제거, libicu 일부를 zstd dictionary로 지연 압축 해제하는 방식도 적용됨
- Rust rewrite, ICU 변경, identical code folding을 합치면 Linux와 Windows에서 Bun 바이너리 크기가 약 **20% 감소**함

| Version | Platform | Size |
| --- | --- | ---: |
| Bun v1.4.0 canary | Windows | 76 MB |
| Bun v1.3.14 | Windows | 94 MB |
| Bun v1.4.0 canary | Linux | 70 MB |
| Bun v1.3.14 | Linux | 88 MB |

- TOML parser와 Bun의 recursive-descent parser들은 stack space를 덜 사용하게 됨
- Rust의 LLVM IR codegen이 stack variable에 `llvm.lifetime.start`와 `llvm.lifetime.end` intrinsic을 내보내 LLVM이 stack slot을 재사용할 수 있음
- 이전에는 Zig의 open issue를 우회하기 위해 특히 큰 함수를 여러 작은 함수로 수동 리팩터링했음
- Rust는 C/C++와 Rust 사이의 cross-language link-time optimization을 지원해 언어 간 inlining이 가능함
- ## Linux x64 벤치마크
  - Bun v1.3.14와 Bun v1.4.0을 Linux x64 EC2 Xeon Platinum 8488C에서 비교함
  - HTTP throughput은 [oha](https://github.com/hatoo/oha), app workload는 [hyperfine](https://github.com/sharkdp/hyperfine)으로 측정함
  - | server | Bun v1.3.14 | Bun v1.4.0 | Δ |
  - | --- | ---: | ---: | ---: |
  - | Bun.serve | 169.6k req/s | 177.7k req/s | +4.8% |
  - | node:http | 103.8k req/s | 108.5k req/s | +4.5% |
  - | Elysia | 158.9k req/s | 163.3k req/s | +2.8% |
  - | express | 64.5k req/s | 66.6k req/s | +3.2% |
  - | fastify | 91.5k req/s | 95.9k req/s | +4.8% |
  - | workload | Bun v1.3.14 | Bun v1.4.0 | Δ |
  - | --- | ---: | ---: | ---: |
  - | next build | 13.62 s | 13.03 s | +4.5% |
  - | vite build | 1.69 s | 1.65 s | +2.2% |
  - | `tsc -b --force` | 0.94 s | 0.89 s | +4.7% |

### 실제 사용 사례와 릴리스 상태
- Prisma는 Bun의 Rust rewrite 위에서 [Prisma Compute](https://www.prisma.io/blog/bun-rust-rewrite-prisma-compute) public beta를 출시함
- Prisma 측은 VM pause/resume 이후 복구되지 않는 connection pool과 memory leak failure mode를 Rust rewrite에서 테스트했고, 해당 failure mode를 잘 처리했다고 밝힘
- Claude Code v2.1.181, 6월 17일 릴리스 이후 버전은 Rust 포트 Bun을 사용함
- Claude Code의 Linux startup은 10% 빨라졌고, 그 외에는 대부분 사용자가 거의 알아차리지 못했다고 함
- Bun v1.3.14는 Zig로 작성된 마지막 Bun 버전임
- Bun v1.4.0은 Rust로 작성된 첫 Bun 버전이며 canary로 제공됨

### 팀이 얻은 도구와 남은 작업
- 새 Rust 코드베이스는 기존 Zig 코드베이스와 매우 비슷한 형태를 유지함
- 원래 Zig 코드를 이해하는 사람은 기계적으로 번역된 Rust 코드도 이해할 수 있도록 작성됨
- Rust rewrite PR 리뷰는 적대적 리뷰 agent가 Zig와 Rust 간 불일치, 포팅 가이드, lifetime guide 준수 여부를 제대로 잡는지 확인하고, 사람이 많은 코드를 side-by-side로 읽는 방식으로 진행됨
- Bun v1.4는 Bun을 더 빠르고 작게 만들고 메모리 사용을 줄이며, 안정성 개선을 위한 도구를 제공함
  - Rust borrow checker
  - Miri
  - LeakSanitizer
  - parser 대상 24/7 coverage-guided fuzzing
- 아직 리팩터링할 부분은 남아 있으며, [bun-unsafe-audit](https://bun.com/bun-unsafe-audit)이 연결됨
- 한 명의 엔지니어가 Fable과 Claude Code를 면밀히 모니터링해, 전체 테스트 스위트가 모든 플랫폼에서 통과하는 상태까지 **11일** 만에 도달함

## Comments



### Comment 61477

- Author: neo
- Created: 2026-07-09T13:05:09+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/6rkdik/rewriting_bun_rust) 
- C++을 썼어도 Bun에는 합리적인 선택이었을 것 같음. 생성자와 소멸자를 얻고 `extern "C"` 래퍼 코드도 많이 지울 수 있었겠지만, 여전히 코드 리뷰로 강제되는 스타일 가이드에 의존해야 하고 ASAN이 있어도 **메모리 손상과 누수**는 계속 났을 것임  
  재미있게도 Node.js는 C++로도 잘 돌아가지만, Bun을 진지한 프로젝트로 본 적은 없음. 이제는 Anthropic 마케팅 부서의 테스트 벤치처럼 보여서 계속 멀리할 생각임
  - Node도 자체 라이브러리 코드에서 나온 **메모리 안전성 CVE**가 분명 있었음. 그냥 "nodejs memory cves"로 검색해도 여러 건이 바로 나옴
  - Node가 C++로 잘 돌아간다는 건 Bun이 Zig로 잘 돌아갔다는 말과 같은 수준임. Node는 관심과 사용자가 더 많아서 문제가 더 빨리 드러날 뿐이고, 결국 둘 다 “아주 조심하고 아무도 실수하지 않으면 완벽하게 동작한다”는 상황에 있음  
    스포일러를 하자면, 실제로는 그렇게 조심하지도 않고 실수도 함
- “Bun의 Rust 코드 중 약 4%가 `unsafe` 블록 안에 있고, 그중 78%는 한 줄짜리”라는 말은 안심시키려는 표현처럼 보이지만, `unsafe` 블록이 한 줄인지 여부는 중요하지 않음. 그 안에서 안전성 보장을 깨면, 블록 밖의 모든 코드도 잠재적으로 **soundness가 깨질 수 있음**  
  Bun의 Rust 포트 초기 병합에는 이런 종류의 명백한 unsoundness가 들어 있었음: https://github.com/oven-sh/bun/issues/30719  
  해당 이슈는 유지보수자들이 CI에서 Rust의 Miri 도구를 켜면서 대응됐고, 글의 “What's Next”에도 *Miri (which runs for a growing chunk of code in CI)*가 포함되어 있으니 그쪽으로 작업 중인 건 좋아 보임  
  공정하게 보자면, 안전성 위반이 있는 Rust라도 대체한 Zig 코드의 품질에 따라 더 유지보수하기 쉬울 수는 있음. 그래도 **unsafe 블록당 코드 줄 수**는 품질 지표가 아니며, 특히 그 블록들에 다른 코딩 관행·전문성·자동 검사도 없었다면 더더욱 그렇지 않음
  - 저 문장은 안심시키려는 게 아니라, `unsafe` 블록이 포팅 과정에서 생긴 게 아니라 **프로젝트 요구사항**에서 나온다는 뜻에 가까워 보임. C 라이브러리를 호출한다면 `unsafe` 블록은 필요하고, 리팩터링만으로 없앨 방법은 없음  
    물론 그 C 라이브러리까지 다시 작성한다면 가능하겠지만, 그건 나중에 검토할 수도 있겠음
- Bun에서 가장 흥미로운 점 중 하나는 기본적으로 **한 명의 엔지니어가 예상보다 훨씬 많은 일을 해낸 이야기**라는 점임. Jarred는 처음엔 Zig 덕분이라고 했고, 이번엔 Claude 덕분이라고 하지만, Jarred 본인이나 그의 업무 윤리 덕분이라고 해도 이상하지 않음. 작은 팀의 힘일 수도 있고, 이미 존재하는 것들을 다시 작성하고 구현한다는 사실 때문일 수도 있음  
  “Bun is joining Anthropic” 발표에서 Jarred는 Bun 작업을 위해 엔지니어를 더 채용한다고 했지만, GitHub만 보면 Bun 팀은 오히려 줄어든 것처럼 보임. 여기서 얻을 결론은 잘 모르겠고, 다만 “Bun은 작은 팀이다” 정도임
- “재작성은 끔찍한 생각”이라고 해놓고 바로 **재작성**을 진행한 셈임  
  방법 자체는 흥미롭지만 글은 마케팅 기사처럼 읽힘. 비용이 얼마나 들었는지에 대한 분석도 부족했고, Rust 재작성에 따르는 위험도 없으며, 애초에 왜 재작성이 일어났는지에 대한 구체적 설명도 약함. 추측하자면 Zig의 [no ai policy](https://ziglang.org/code-of-conduct/#strict-no-llm-no-ai-policy) 때문이거나, Anthropic 내부에서 Rust에 집중하려는 방침이 있었을 수도 있음
  - 비용에 대해서는 이 대목이 적어도 관련 있어 보임: “병합 전까지 캐시되지 않은 입력 토큰 59억 개, 출력 토큰 6억 9천만 개, 캐시된 입력 토큰 읽기 720억 개가 쓰였고, API 가격 기준 약 **165,000달러**였다”  
    그리고 왜 재작성했는지는 글이 꽤 일관된 이야기를 하고 있다고 봄. Bun은 문제를 잡기 위한 조치를 했는데도 계속 크래시가 났고, 개발자들은 이런 문제를 더 체계적으로 막을 방법을 원했음  
    처음 계획은 특정 코딩 스타일을 더 엄격히 강제하고 스마트 포인터를 도입하는 쪽이었지만, Jared는 자체 스마트 포인터가 Rust보다 사용성이 나쁘고 보장도 없다고 봤음. 그래서 “Anthropic의 새 모델이 Bun을 Rust로 다시 쓸 수 있는지 일주일 테스트해보면 어떨까?”가 되었고, 테스트 스위트 통과율이 높아지자 “해볼 만하다”에서 “병합하겠다”로 바뀐 흐름으로 보임  
    즉 처음부터 Rust 재작성을 결정했다기보다는, “Rust가 문제 해결책을 주는 것 같지만 재작성 비용 때문에 못 한다. 그런데 LLM 포팅을 해보니 가능성이 있다. 그러면 LLM 재작성으로 가자”에 가까워 보임
  - “병합 전까지 캐시되지 않은 입력 토큰 59억 개, 출력 토큰 6억 9천만 개, 캐시된 입력 토큰 읽기 720억 개가 쓰였고, API 가격 기준 약 **165,000달러**였다”
  - 재작성 이유는 충분히 정당해 보임. 계속되는 **메모리 안전성 문제**와 끝없는 두더지 잡기식 버그 수정에 대한 두려움이 있었고, 일부 엔지니어는 다른 방식으로 대응했겠지만 LLM으로 Rust 재작성을 선택하는 것도 문제에 대한 하나의 해결책임
  - 확실히 마케팅 기사처럼 읽힘. 의도했든 아니든 그렇지만, 실제로는 비용 계산이 불가능에 가까움. 특히 개발자들이 출시 전 Anthropic 모델에 접근할 수 있었다면 더 그렇다. 다만 지금의 Claude 모델로 평범한 우리가 그 정도 규모를 다시 쓴다면 비용은 추정할 수 있음
- 이런 버그들을 계속 하나씩 고치는 대신, 의존하는 사용자들을 위해 더 잘해야 하고 이런 버그가 반복되지 않게 체계적으로 막아야 한다는 건 훌륭한 이유임. 그래도 “**Rust 분위기가 좋다**”는 이유였어도 받아들였을 것 같음  
  이걸 해낸 Jarred, Bun 팀, Anthropic에게 축하를 보냄
