Bun이라고 Node.js의 대안으로 나온 프로젝트가 있었습니다.
해당 프로젝트는 Zig라는 언어로 이루어져 있었고, 많은 관심을 받은 상태였습니다.
그러던중, 어느 날 하나의 PR이 올라옵니다.
Zig로 이루어진 프로젝트를 Rust로 재포팅한다는 pr이였습니다.
바로 GeekNews에 올라왔고, 많은 관심이 쏠리게 되었습니다.
bun이 왜 Zig에서 Rust로 언어를 바꾸게 되었고, 이로 인해 어떤 이득을 얻게 되었는지에 대하여 다뤄보고자 합니다.
왜 언어를 바꾸게 되었는가?
Bun은 esbuild의 JavaSciprt, TypeSciprt 트랜스파일러를 Go에서 Zig로 라인 단위 포팅한 프로젝트로 출발했습니다.
2021년 4월 16일에 저수준 제어와 성능 지향 설계에 매료되어 Zig를 사용하여작성되었습니다.
JavaScript는 GC를 지원하는 언어이며, JavaScirptCore(V8)와 같은 최신 JavaScript 엔진은 예외 처리 및 GC에 대한 엄격한 규칙을 가지고 있었습니다.
Zig는 C처럼 메모리를 자동 관리하지 않으며, 생성자와 소멸자가 없고 cleanup은 대부분 call site에서 defer로 명시해야 했습니다.
Bun에서는 GC 값과 수동 메모리 관리 값의 lifetime을 올바르게 처리하는 일이 안정성 문제의 큰 원천이였습니다.
이 바이트는 어디에서 해제되는가?, 한 번만 해제되도록 어떻게 보장하는가?, JavaSciprt 예외를 제대로 확인했는가?, 이 메모리는 GC되는 메모리인가, 아니면 수동으로 관리되는 메모리인가와 같은 부분을 꼼꼼히 검토해야 했습니다.
Bun 코드의 약 20%는 C++이였고, 여러 C/C++ 라이브러리를 내장하고 있어, C++도 선택지가 될 수 있었지만, 여전히 스타일 가이드와 코드 리뷰에 의존하고 메모리 손상과 누수가 발생할 수 있었습니다.
Bun v1.3.14버전에서 메모리 관련 많은 문제들이 발생하고 있었습니다.
많은 언어들 중에 왜 Rust인가?
버그들 중 상당수는 use-after-free, double-free와 같은 문제들이였습니다. 이러한 문제들은 Rust에서 컴파일러 오류로 처리되고, 컴파일러 오류는 스타일 가이드보다 더 나은 피드백 루프 역할을 합니다.
새로운 언어로 프로젝트를 다시 작성하는 것은 최악의 선택입니다. Bun은 주석을 제외한 535,496의 Zig 코드로 이루어져 있었고, 이를 다른 언어로 다시 작성할려면 소규모 엔지니어 팀이 1년이라는 시간을 투자해야합니다.
출시 가능한 버전을 만드는 가장 안전한 방법은 최소한 동작 변경만 거쳐 Zig에서 Rust로 기계적으로 포팅하는 것이며, Bun 테스트에 이미 사용하고 있는 동일한 테스트 스위트를 그대로 사용하는 것입니다.
어떻게 Zig에서 Rust로 바꾸었는가?
Claude Code에서 약 50개의 동적 워크플로우가 11일동안 계속 실행되었습니다.
각 동적 워크플로우는 다음과 같은 루프를 진행했습니다.
- Zig 패턴 및 타입을 Rust 패턴 및 타입에 매핑하는 포팅 가이드를 생성합니다.
- PORTING.md 및 LIFETIMES.tsv .zig 파일과 일치하는 파일 .rs로 모든 파일을 기계적으로 포팅합니다
- 모든 크레이트의 컴파일러 오류를 수정하세요
- bun test또는 와 같은 하위 명령이 bun build작동하도록 하세요
- Bun의 전체 테스트 스위트에 있는 모든 테스트를 통과시키세요
- 여러 차례의 대규모 리팩토링 및 정리 작업
LLM이 만든 코드를 어떻게 검토했는가?
각 구현 Claude와 별도 context winodw에 적대적 리뷰어 Claude를 두고, 리뷰어는 diff만 받은 상태에서 코드가 틀렸다고 가정하고 버그를 찾도록 지시했습니다.
기본 구조는 1명의 구현자와 2명이상의 적대적 리뷰어, fixer 1명으로 구성되었습니다.
실제로 리뷰어가 잡은 버그는 모든 컴파일은 통과했지만 동작상의 문제가 발생했었습니다.
여러 Claude가 git stash, git stash pop, git reset HEAD --hard를 실행하며 서로 충돌하여, 워크플로우에 특정 파일 커밋이 아닌 git명령, cargo 같은 느린 명령을 금지하는 규칙을 추가했습니다.
병렬화와 사전 준비 덕분에 Cluade는 분당 1300줄 코드를 작성했습니다.
문제는 없었는가?
Zig의 코드베이스는 사실상 하나의 compilation unit이었고, Rust 코드는 더 빠른 컴파일을 위해 약 100개 crate로 나누려 했습니다.
순환 의존성 문제가 있었고, 이를 해결하는 과정에서 16000개의 컴파일 오류가 생겼습니다.
64개의 Cluade로 이를 동시에 처리하였습니다. Cluade는 모든 crate를 컴파일되게 하자를 함수를 stub 처리하라로 해석해서 실패하는 경우도 있었습니다. 문단 길이 주석이 필요하면 코드는 틀렸고 코드를 고쳐야 한다라는 규칙을 만들어서 몇번의 수정을 거친 이후, 이러한 문제가 발생하지 않았습니다.
느낀점
Bun에서 여러 메모리 관련 문제가 있었고, 이를 Rust로 RIIR(Rewrite it in Rust)한 케이스 중 좋은 예시라고 생각합니다.
Anthropic에 Bun이 인수되고 나서, 언어 변경을 진행하는데 Claude라는 AI를 활용한 좋은 케이스라고도 생각합니다.
AI의 발달로 인하여, 앞으로 Bun과 같은 RIIR를 진행하는 프로젝트가 점차 더 많아질거라고 생각합니다.
'리뷰 > 테크 블로그' 카테고리의 다른 글
| 왜 kt cloud는 FastAPI 대신 Robyn을 선택했을까? (0) | 2026.04.16 |
|---|
댓글