- Published on
Bun의 Zig to Rust 재작성 11일 — AI 대규모 마이그레이션에서 실제로 옮겨 갈 수 있는 것
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 재작성보다 글 쓰는 데 시간이 더 걸린 프로젝트
- 숫자로 본 11일
- 이 코드베이스가 유난히 옮기기 좋았던 세 가지 조건
- 사람이 여전히 해야 했던 일 — 코드가 아니라 루프를 고친다
- 반론 — "리뷰되지 않은 슬롭"과 6주 뒤의 저장소
- 이 방법이 당신의 코드베이스에서 무너지는 지점
- 마치며 — 11일은 AI의 성능이 아니라 오라클의 함수였습니다
- 참고 자료
들어가며 — 재작성보다 글 쓰는 데 시간이 더 걸린 프로젝트
2026년 7월 8일, Bun이 Rewriting Bun in Rust를 공개했습니다. 작업 자체는 5월 3일에 시작해 5월 14일에 메인 브랜치로 병합됐으니, Jarred Sumner은 11일 만에 끝낸 일을 정리하는 데 두 달 가까이 쓴 셈입니다. Simon Willison이 자기 블로그에서 "5월 9일부터 약속해 온 글"이라고 짚은 것도 같은 얘기입니다.
숫자는 이미 충분히 돌았습니다 — 주석을 뺀 53만 5,496줄의 Zig, 1,448개의 .zig 파일, 6,502개의 커밋, 최대 64개의 동시 Claude 인스턴스, API 정가 환산 약 16만 5천 달러. 이 숫자들은 인용하기 좋지만 옮겨 갈 수 있는 것은 아닙니다. 당신의 팀이 내년에 레거시 서비스를 옮길 때 쓸 수 있는 것은 "11일"이 아니라, 11일이 성립하기 위해 미리 갖춰져 있어야 했던 조건입니다.
그리고 그 조건은 생각보다 까다롭습니다. 이 글은 Bun의 발표, Anthropic의 방법론 정리, Zig 창시자 Andrew Kelley의 반론, 그리고 병합 6주 뒤의 실제 저장소 상태를 나란히 놓고, 이 접근이 어디서 성립하고 어디서 무너지는지를 봅니다. 결론부터 말하면 오라클 테스트 스위트가 있는 재작성은 마이그레이션 중에서 가장 쉬운 경우입니다. 그 사실을 인정하는 데서 시작해야 나머지가 쓸모 있어집니다.
숫자로 본 11일
Bun 발표문과 Anthropic 정리에 실린 수치를 한 표로 모았습니다. 두 문서가 같은 값을 말하는 항목만 실었고, 갈리는 항목은 아래 본문에서 따로 짚습니다.
| 항목 | 값 |
|---|---|
| 기간 | 2026-05-03 ~ 05-14 (11일), 전 플랫폼 그린은 빌드 #54202 |
| 원본 규모 | Zig 535,496줄(주석 제외), 1,448개 파일, 이 밖에 약 20퍼센트가 C++ |
| 최종 디프 | 1,009,272줄 추가, 커밋 6,502개(머지 포함 6,778개) |
| 병렬도 | 피크 64 인스턴스(워크플로 4개 × 16), 전체 약 50개 동적 워크플로 |
| 모델 | 프리릴리스 Claude Fable 5, 리뷰·규칙 생성에 Opus 4.8 |
| 토큰 | 비캐시 입력 59억, 출력 6.9억, 캐시 입력 읽기 720억 |
| 비용 | API 정가 환산 약 16만 5천 달러 |
| 테스트 | 삭제·스킵된 테스트 0개, 6개 플랫폼, 플랫폼당 5.7만~6만 개 테스트 |
| unsafe 비율 | 약 78만 줄 중 unsafe 블록이 약 2만 7천 줄(약 4퍼센트), 그중 78퍼센트가 1줄짜리 |
| 병합 후 회귀 | 알려진 것 19건, 모두 수정 |
| 성능 | HTTP 처리량 2.8~4.8퍼센트 향상, 바이너리 약 20퍼센트 축소 |
성능 항목이 이 표에서 가장 조용한 줄입니다. 16만 5천 달러를 쓰고 얻은 것이 처리량 몇 퍼센트라면 그 자체로는 투자 근거가 되지 않습니다. Bun이 실제로 산 것은 속도가 아니라 메모리 안전성이었습니다 — Anthropic 정리에 인용된 누수 벤치마크는 2,000회 빌드 후 누적 메모리가 6,745MB에서 609MB로 떨어졌습니다(Bun 발표문은 같은 사건을 빌드당 3.3MB 누수가 사라진 것으로 다르게 서술합니다. 두 문서의 표현이 어긋나므로 이 수치는 단일 출처로 취급하는 편이 안전합니다). 사용자가 체감한 변화는 Linux p50 기준 시작 시간이 517ms에서 464ms로 10퍼센트 빨라진 것 정도였고, Willison의 표현대로 "지루한 게 좋은" 결과였습니다.
이 코드베이스가 유난히 옮기기 좋았던 세 가지 조건
첫째, 언어에 독립적인 오라클이 이미 있었습니다. Bun의 테스트 스위트는 TypeScript로 작성돼 있습니다. 런타임을 Zig로 짜든 Rust로 짜든 테스트 코드는 한 글자도 바뀌지 않습니다. 플랫폼당 5만 7천 개가 넘는 테스트와 100만 개 이상의 assertion이, 이식이 끝났는지를 사람 판단 없이 판정해 줍니다. 삭제되거나 스킵된 테스트가 0개라는 사실이 중요한 이유가 여기 있습니다 — 심판을 손보지 않았다는 뜻이니까요.
이것이 얼마나 드문 조건인지는 반대 사례를 떠올려 보면 압니다. 테스트가 내부 구현에 붙어 있는 코드베이스(모듈 내부 함수를 직접 부르거나, 프라이빗 상태를 검사하는 테스트)는 언어를 바꾸는 순간 테스트도 같이 버려야 합니다. 심판이 사라진 마이그레이션은 그냥 대규모 무검증 커밋입니다. Anthropic 문서가 여섯 단계 중 0단계로 "심판(the judge)을 먼저 세우라"고 못 박고, 일부러 망가뜨린 코드에 심판을 돌려 떨어지는지부터 확인하라고 쓴 것은 이 때문입니다.
둘째, 구조 보존 번역이었습니다. 1,448개의 .zig 파일이 그만큼의 .rs 파일로 갔습니다. 아키텍처를 다시 설계하지 않았기 때문에 작업 단위가 파일 하나로 떨어지고, 파일 간 의존성 그래프만 알면 병렬화 순서가 결정됩니다. 그리고 "끝났는지"를 판정하는 방법이 사람의 판단이 아니라 디스크 상태입니다 — 출력 파일이 존재하면 완료입니다. Anthropic이 공개한 마이그레이션 키트가 큐를 이렇게 정의한 덕에 중단과 재개가 공짜가 됩니다.
셋째, 대상 언어의 컴파일러가 두 번째 심판이었습니다. Rust는 소유권과 수명을 컴파일 타임에 강제하므로, 번역이 어설프면 테스트까지 갈 것도 없이 빌드가 깨집니다. Pragmatic Engineer의 정리에 따르면 약 1,600개의 컴파일 오류를 12시간 만에 걷어냈는데, 이 오류 목록 자체가 다음 작업 큐를 자동으로 써 주는 역할을 합니다. 강타입 언어에서 강타입 언어로 가는 이식은 이 되먹임이 공짜로 붙습니다. Python에서 Python으로 가는 리팩터링에는 이 심판이 없습니다.
세 조건을 한 문장으로 줄이면 이렇습니다 — 기계가 판정할 수 있는 정답이 두 겹으로 이미 존재했습니다. AI가 잘해서 11일이 나온 게 아니라, 11일 동안 기계가 스스로 채점할 수 있는 문제였기 때문에 11일이 나왔습니다.
사람이 여전히 해야 했던 일 — 코드가 아니라 루프를 고친다
발표문에서 가장 실무적인 대목은 코딩 이전의 세 시간입니다. Sumner는 Zig의 관용구를 Rust로 어떻게 옮길지 Claude와 약 3시간 논의했고, 그 결론이 약 600줄짜리 PORTING.md로 직렬화됐습니다. Anthropic 키트가 이 문서에 붙인 메타 규칙이 정확합니다 — 두 에이전트가 서로 다르게 답할 수 있는 판단이면 룰북에 들어가야 합니다.
그다음이 3개 파일짜리 예행 연습입니다. 파일 하나마다 구현자 한 명과 적대적 리뷰어 두 명을 붙여 돌려 보고, 나온 번역물은 전부 버리고 규칙만 고칩니다. 이 단계에서 두 건의 문제가 잡혔는데, 그대로 갔다면 1,448개 파일에 전부 번졌을 것들이었습니다. 세 시간과 세 파일에 사람 시간을 몰아 넣는 이 배치가 이 방법론의 핵심입니다.
본 작업에서 사람이 개입한 지점도 개별 파일이 아니라 계통이었습니다. 대표적인 게 순환 의존입니다. Zig는 지연 컴파일 덕에 순환 임포트를 견디지만 Rust의 모듈 시스템은 그렇지 않아서, 수천 개의 모듈 오류가 한꺼번에 터졌습니다. 이때 사람이 한 일은 파일을 고치는 게 아니라 의존성을 삭제·이동·경계 재구성 중 어디로 분류할지를 로직으로 인코딩하는 것이었습니다. Anthropic 문서가 반복해서 쓰는 표현이 이 태도를 요약합니다 — 코드를 고치는 게 아니라 그 코드를 만들어 낸 루프를 고칩니다.
키트가 권하는 큐 구조를 최소한으로 옮기면 이런 모양입니다. 완료 판정에 사람이나 모델이 끼지 않는다는 점이 전부입니다.
# manifest.tsv: <원본경로>\t<대상경로> (의존성 그래프 순서로 정렬)
# "완료"의 정의 = 대상 파일이 디스크에 존재한다. 그 외의 판정 기준은 두지 않는다.
while IFS=$'\t' read -r src dst; do
[ -f "$dst" ] && continue
echo "$src -> $dst"
done < migration/manifest.tsv > migration/queue.txt
wc -l < migration/queue.txt # 남은 작업량. 중단은 무료, 재개는 재실행일 뿐이다.
이 구조가 왜 중요한지는 반대 경우를 생각하면 명확합니다. "이 파일은 다 옮겼나요"를 에이전트에게 묻는 순간, 진척도 자체가 모델 출력이 되고 진행 상황이 환각의 대상이 됩니다.
반론 — "리뷰되지 않은 슬롭"과 6주 뒤의 저장소
발표 다음 날인 7월 9일, Zig 창시자 Andrew Kelley가 반론을 냈습니다. The Register가 뽑은 "리뷰되지 않은 슬롭(unreviewed slop)"이라는 표현이 헤드라인을 먹었지만, 그가 던진 논증 자체는 훨씬 날카롭습니다.
핵심은 테스트 스위트의 자기모순입니다. 100만 줄의 미리뷰 코드를 넣어도 된다는 근거가 테스트 스위트라면, 그 테스트 스위트는 왜 기존 Zig 코드의 버그들은 잡지 못했는가. Bun 스스로 옛 코드에 메모리 버그가 많았다고 말하고 있으니, 같은 심판이 새 코드는 충분히 검증한다는 주장은 성립하기 어렵다는 것입니다. 여기에 Kelley는 문제의 원인이 언어가 아니라 프로젝트의 가치 체계라고 덧붙입니다 — "핵심 쟁점은 Zig와 Rust의 언어 기능과는 아무 관계가 없고, 두 프로젝트의 가치 체계가 갈라진 데 있다"는 문장이 그 요지입니다.
이 지적은 옳고, 동시에 방향이 하나 빠져 있습니다. 오라클이 잡지 못한 결함군을 대상 언어의 타입 시스템이 구조적으로 제거하는 경우가 실제로 있습니다 — use-after-free와 이중 해제는 Rust에서 unsafe 밖으로 나가면 문법적으로 표현되지 않습니다. 그래서 "테스트가 못 잡은 버그"의 상당 부분은 테스트가 아니라 컴파일러가 잡습니다. 다만 그 논리는 unsafe 비율만큼 약해지고, Bun의 경우 약 4퍼센트, 약 2만 7천 줄입니다. HN 토론에서 나온 반박이 정확히 이 지점을 찍었습니다 — 5만 줄 Rust 프로젝트에서 unsafe가 9줄인 사례와 비교하면, 2만 7천 줄은 "안전한 Rust"라고 부를 규모가 아닙니다.
병합 6주 뒤의 상태도 같이 봐야 공정합니다. Tom Lockwood가 7월 27일에 저장소를 직접 확인한 바로는, 마지막 릴리스 태그가 5월 12일의 v1.3.14였고 Rust 기반 v1.4.0은 그때까지 캐나리 상태였습니다. 봇 계정이 연 열린 PR은 7월 9일 1,277개에서 7월 27일 2,475개로 늘었고, Lockwood는 CI 비용과 이어지는 토큰 사용을 합치면 실제 비용이 80만 달러에 근접할 것으로 추정합니다. 이 추정치는 외부 관찰자의 계산이고 Bun이나 Anthropic이 확인한 값이 아니므로 그대로 인용할 숫자는 아닙니다.
반대편 사실도 분명합니다. Rust 기반 Bun은 6월 17일 릴리스된 Claude Code v2.1.181에 실려 이미 수백만 명에게 돌고 있었습니다. 즉 "프로덕션에서 돈다"와 "릴리스가 끝났다"가 6주 넘게 따로 놀았다는 것이고, 이 간극이야말로 마이그레이션 계획에 넣어야 할 항목입니다. 병합은 끝이 아니라 잔여 작업 큐가 시작되는 지점입니다.
이 방법이 당신의 코드베이스에서 무너지는 지점
오라클이 없으면 여기서 끝입니다. 이 방법론은 사람 리뷰를 기계 판정으로 대체해서 속도를 얻습니다. 판정자가 없으면 대체된 게 아니라 그냥 생략된 것입니다. HN 스레드에서 나온 예시가 좋습니다 — 게임 엔진을 같은 방식으로 옮기려면 무엇을 심판으로 쓸 것인가. 출력이 픽셀과 소리인 시스템에는 이식 가능한 단위 테스트가 애초에 잘 만들어지지 않습니다. 같은 문제가 UI 레이어, 배치 리포팅, 하드웨어 제어에도 그대로 적용됩니다.
재설계가 섞이면 병렬화가 무너집니다. Bun은 구조를 그대로 두었기 때문에 파일 단위 큐가 성립했습니다. 옮기면서 모듈 경계를 다시 그리기로 하면 작업 단위가 파일이 아니라 모듈이 되고, 원본과 대상을 줄 단위로 대조할 수 없게 되며, 3파일 예행 연습이라는 검증 장치 자체가 무의미해집니다. Anthropic 키트가 재설계형은 "룰북이 설계 문서로 바뀌고 예행 연습이 설계 리뷰로 바뀐다"고 따로 떼어 놓은 이유입니다. 옮기기와 다시 그리기를 한 번에 하려는 유혹이 이 방법론의 가장 흔한 실패 원인입니다.
리뷰 경제가 성립하지 않습니다. 100만 줄을 사람이 읽을 방법은 없습니다. Bun은 그것을 인정하고 적대적 리뷰어와 컴파일러와 테스트에 판정을 넘겼습니다. 이 선택이 옳았는지는 사실 아직 결론이 나지 않았습니다 — 알려진 회귀 19건은 "발견된 것"이고, 발견되지 않은 것의 수는 정의상 알 수 없습니다. HN에서 한 개발자가 남긴 반응이 이 긴장을 잘 보여 줍니다. 2주에 회귀 19건을 낸다면 자기 회사에서는 고객에게 사과문을 쓰고 있을 거라는 것입니다. 22만 다운로드가 아니라 2,200만 다운로드 규모의 런타임에서, 19라는 숫자를 크다고 볼지 작다고 볼지는 도메인이 정합니다.
그리고 이 규모의 재작성은 여전히 예외적인 선택입니다. Anthropic 문서가 솔직하게 적어 둔 것처럼, 이런 프로젝트가 예전에 4년에 300만~400만 달러였다면 지금은 16만 5천 달러이고, 그래서 "존재를 건 정당화"가 필요 없어졌습니다. 문턱이 낮아진 것은 사실입니다. 하지만 낮아진 문턱이 곧 좋은 결정이라는 뜻은 아닙니다. 옮길 이유가 만성 병목 하나(빌드 시간, 메모리 버그 이력)로 요약되지 않는다면, 옮기지 않는 편이 여전히 기본값입니다.
마치며 — 11일은 AI의 성능이 아니라 오라클의 함수였습니다
정리하면 이렇습니다.
- 11일을 만든 것은 모델이 아니라 언어에 독립적인 테스트 스위트 + 파일 대 파일 구조 보존 + 강타입 컴파일러라는 세 겹의 기계 판정입니다. 이 셋 중 하나라도 빠지면 같은 방법이 같은 속도를 내지 않습니다.
- 사람이 쓴 시간은 앞쪽에 몰려 있었습니다. 3시간의 룰북 논의와 3개 파일 예행 연습이, 1,448개 파일에 번질 문제를 미리 잘랐습니다. 그 뒤의 사람 개입은 개별 코드가 아니라 계통 오류의 분류 규칙이었습니다.
- 비판도 실체가 있습니다. 테스트가 못 잡은 버그를 근거로 재작성을 정당화하면서 같은 테스트로 새 코드를 보증하는 논증에는 구멍이 있고, unsafe 약 2만 7천 줄은 "안전해졌다"는 서술을 상당히 약하게 만듭니다.
- 병합과 릴리스 사이에 6주 이상의 잔여 작업이 있었습니다. 마이그레이션 일정에서 병합일을 완료일로 잡으면 그 구간이 통째로 사라집니다.
오라클이 있는 재작성은 마이그레이션 중 가장 쉬운 문제입니다. 당신의 프로젝트에서 먼저 확인할 것은 모델도 예산도 아니라, 옮기기 전과 옮긴 뒤에 똑같이 돌릴 수 있는 테스트가 있느냐입니다. 없다면 이 글의 11일은 참고 자료가 아니라 다른 세계의 이야기입니다.
참고 자료
- Rewriting Bun in Rust — Bun 공식 발표문 (2026-07-08)
- How Anthropic runs large-scale code migrations with Claude Code — 방법론 정리
- anthropics/code-migration-kit-with-claude-code — 프롬프트·템플릿·스크립트
- Andrew Kelley — My Thoughts on the Bun Rust Rewrite (2026-07-09)
- The Pragmatic Engineer — What can we learn from Bun's rapid Rust rewrite with AI?
- Simon Willison — Rewriting Bun in Rust (2026-07-08)
- Tom Lockwood — How is the Bun Rewrite in Rust Going? (2026-07-27, 외부 추정치 포함)
- Hacker News — Rewriting Bun in Rust 토론
- AI로 코드베이스를 옮기는 실전 절차 — 방법론 편 (관련 글)