본문 바로가기

IT & AI

Rust 30만 줄을 Zig로 다시 쓴 Roc 컴파일러, 487일 뒤 얻은 것

728x90

Rust 30만 줄을 Zig로 다시 쓴 Roc 컴파일러, 487일 뒤 얻은 것

Roc 컴파일러 Rust에서 Zig 재작성 썸네일
Roc 컴파일러 Rust에서 Zig 재작성 썸네일

Roc 팀이 기존 Rust 컴파일러 약 30만 줄을 Zig로 다시 쓰고 487일 만에 기능 동등성에 도달했어요. 언어만 바꾼 이식이 아니라 컴파일러 구조와 개발 경험까지 손본 재작성에 가까워요. 올해 후반에는 Roc의 첫 정식 번호 릴리스인 0.1.0을 내놓을 계획이에요. 1

핵심 요약

구분핵심왜 볼 만한가요
재작성 범위Rust 약 30만 줄을 Zig로 옮기며 여러 컴파일러 단계를 다시 설계했어요언어 교체보다 자료구조와 빌드 구조를 바꾼 과정에 가까워요
빌드 속도Zig 0.17.0 개발 버전에서 약 46만 줄을 35ms에 증분 빌드했어요저장 직후 오류와 테스트 결과를 받는 개발 흐름을 노렸어요
결과물Rocci Bird의 wasm 파일이 31KB로 줄었어요기존 컴파일러 결과의 절반보다 작은 실제 산출물이 나왔어요
메모리 안전성Zig 구현에서 컴파일러 자체 use-after-free 2건이 확인됐어요Rust의 빌림 검사기가 막았을 오류와 Zig의 제어력을 함께 비교할 수 있어요
남은 조건35ms 증분 빌드는 Zig 0.17.0과 x86-64에 기대고 있어요아직 모든 개발자가 같은 속도 이점을 누리지는 못해요

1. 언어 교체보다 컴파일러 구조를 다시 짠 작업이에요

Roc은 참조 카운팅과 Perceus 최적화를 쓰는 함수형 프로그래밍 언어예요. 기존 컴파일러에는 여러 단계에 걸친 구조적 문제가 있었고, 일부만 고쳐서는 해결하기 어려웠어요. 팀은 파서와 타입 검사기, 데이터 배치, 캐시, 코드 생성 경로를 폭넓게 다시 설계했어요. 그래서 이번 작업을 Rust 코드를 Zig 문법으로 옮긴 단순 이식으로 보면 실제 범위를 놓치게 돼요. 2

재작성은 487일이 걸렸어요. 최근 Bun이 Zig 약 50만 줄을 Rust로 옮긴 작업은 11일이었지만, 두 기간을 언어 생산성 비교로 쓰기는 어려워요. Bun은 기존 동작을 최대한 유지한 직접 이식에 가까웠고, Roc은 여러 기능과 내부 구조를 함께 바꿨어요. 원문도 이 차이를 분명히 밝혀요.

728x90

기능 동등성에 도달하면서 실제 결과물도 확인됐어요. 1,000줄이 안 되는 WASM-4 게임 Rocci Bird를 새 컴파일러로 빌드할 수 있게 됐고, 크기 최적화 빌드가 만든 wasm 파일은 31KB예요. 기존 컴파일러가 만든 파일의 절반보다 작아요. Roc 홈페이지에서는 2.5MB WebAssembly 컴파일러로 기본 프로그램을 브라우저에서 작성하고 실행할 수도 있어요.

저장하자마자 새 코드를 반영하는 개발 흐름을 넣었어요

새 컴파일러는 개발 중 핫 코드 로딩을 지원해요. 서버를 실행한 채 소스를 고치면 다음 요청부터 바뀐 코드가 적용돼요. 배포할 때는 LLVM 최적화를 적용한 독립 실행 파일을 만들 수 있어요. Alpine Linux용 정적 바이너리 같은 다른 대상도 크로스 컴파일해요.

재현 가능한 빌드도 목표로 삼았어요. 같은 소스 바이트를 입력하면 macOS를 포함한 서로 다른 빌드 환경에서 같은 출력 바이트가 나오도록 설계했어요. 패턴 매칭 안에서 문자열 보간을 지원해 HTTP 경로를 나눌 수 있고, 원문 예제의 라우팅 코드는 요청 처리 중 힙 할당을 하지 않아요.

이 기능들은 Zig를 썼다는 사실만으로 생기지 않았어요. 컴파일러 구조를 바꾸고, 컴파일 시점 실행과 데이터 배치를 새로 설계한 결과예요. 언어 선택과 아키텍처 변경의 효과를 분리해 볼 필요가 있어요.

35ms 증분 빌드는 빠르지만 조건이 붙어요

Roc 팀이 공개한 측정에서 Rust 1.97.0 기반 원본은 35만 4,000줄을 증분 빌드하는 데 3.4초가 걸렸어요. Zig 0.17.0 기반 재작성본은 46만 4,000줄을 약 35ms에 다시 빌드했어요. 코드가 약 50% 더 많은데도 증분 빌드는 약 100배 빨랐어요.

35ms는 단순한 대기 시간 절감보다 피드백 주기를 바꿔요. 저장할 때마다 컴파일 오류와 무효화된 테스트 결과를 거의 바로 확인할 수 있어요. 컴파일러를 몇 분마다 한 번 실행한다면 3.4초도 짧지만, 감시 모드에서 매번 실행하면 체감 차이가 커져요.

다만 현재 수치는 안정판의 일반적인 조건이 아니에요. Roc 코드베이스에서 증분 빌드를 제대로 쓰려면 호환성 변경이 있는 Zig 0.17.0 개발 버전이 필요해요. x86-64에서만 작동하고, ARM 기반 Mac을 쓰는 기여자는 아직 같은 이점을 누리지 못해요. 콜드 빌드는 Zig 재작성본이 32.1초, Rust 1.97.0 원본이 25.4초라서 Zig 쪽이 더 느렸어요.

Zig는 Roc의 메모리 배치 방식과 잘 맞았어요

Roc 컴파일러는 단계와 모듈마다 별도 아레나 할당자를 많이 써요. 자료구조는 포인터 대신 32비트 배열 인덱스를 널리 사용하고, 구조체 배열 형태로 데이터를 배치해요. 이 구조는 메모리 사용량을 줄이고 디스크 캐시를 단순하게 만들어요.

새 캐시는 실행할 때 쓰는 메모리 배치를 디스크에 그대로 기록해요. 다시 읽을 때 별도 형식으로 파싱하기보다 바이트를 메모리로 옮기고 배열 위치를 조정해 바로 사용해요. 입력 파일이 바뀌지 않으면 파싱과 타입 검사 결과를 재사용할 수 있어요. 이런 방식에는 할당자와 데이터 배치를 세밀하게 다루는 Zig의 API가 잘 맞았다고 팀은 설명해요.

LLVM과 연결하는 방식도 달라졌어요. 자주 바뀌는 LLVM C++ API를 직접 감싸기보다 안정적인 bitcode 형식을 쓰는 직렬화기를 재사용했어요. 필요한 구현이 Zig 컴파일러에 이미 있었기 때문에 Roc이 Zig 생태계에서 얻은 중요한 자산이 됐어요.

메모리 안전성 비교는 한 줄로 결론 내리기 어려워요

기존 Rust 코드에는 약 1,200개의 `unsafe` 사용이 있었어요. Roc 팀은 아레나와 인덱스 기반 구조, 컴파일 시점 코드 실행처럼 저수준 제어가 잦은 작업에서 Zig가 더 직접적인 도구를 제공한다고 봤어요. 반면 Zig에는 Rust의 빌림 검사기처럼 수명 오류를 컴파일 단계에서 막는 장치가 없어요.

실제 이슈 분류에서 Rust 컴파일러의 메모리 손상 관련 보고는 21건, Zig 컴파일러는 10건이었어요. Rust 쪽 21건과 Zig 쪽 8건은 컴파일러 프로세스의 메모리가 깨진 문제가 아니라 잘못된 기계어를 만든 오컴파일이었어요. Zig 쪽 나머지 2건은 오류 메시지의 파일명을 깨뜨린 use-after-free였고, Rust의 빌림 검사기라면 막을 수 있었어요.

이 숫자만으로 Zig가 Rust만큼 안전하다고 말할 수는 없어요. 전체 보고 건수와 테스트 범위가 다르고, Zig의 런타임 검사는 실행하지 않은 경로를 확인하지 못해요. 원문의 ReleaseSafe 설명을 두고도 공개 토론에서 기술적 반론이 나왔어요. Roc 사례가 보여 주는 범위는 이 프로젝트의 자료구조와 테스트 방식에서 선택한 절충이에요.

Zig에서 포기한 Rust의 편의도 분명해요

Roc 팀은 테스트 코드에서 Rust의 자동 자원 정리를 그리워했어요. Zig에서는 `init`과 `deinit`을 직접 맞춰야 하고, 하나라도 빠지면 테스트가 메모리 누수로 실패할 수 있어요. Rust의 trait 기반 다형성, 비공개 구조체 필드, 죽은 코드 탐지, 비교적 매끄러운 버전 업그레이드도 Zig보다 편했다고 평가했어요.

Zig는 현재 하위 호환성을 목표로 하지 않아요. 개발 버전을 따라가면 의존성을 함께 고치거나 벤더링해야 할 수 있어요. Roc 팀은 이 비용을 예상하고 선택했지만, 같은 결정을 검토하는 팀이라면 컴파일 속도만큼 유지보수 비용도 계산해야 해요.

왜 중요한가요

대규모 재작성의 성패는 새 언어 이름보다 기존 문제를 얼마나 구체적으로 정의했는지에 달려 있어요. Roc 팀은 빠른 증분 빌드, 단계별 할당자, 인덱스 기반 데이터 배치, 재현 가능한 크로스 컴파일처럼 원하는 결과를 먼저 정했어요. Zig는 이 구조에 맞는 선택지였지만, 487일의 작업 대부분은 컴파일러를 다시 설계하고 구현하는 데 쓰였어요. 2

비슷한 전환을 검토하는 개발팀은 콜드 빌드와 증분 빌드를 따로 재야 해요. 사용하는 CPU 아키텍처, 개발 버전 의존성, 테스트 작성 비용, 메모리 오류를 발견하는 방법도 함께 봐야 해요. Roc의 35ms 결과만 가져오면 ARM 환경의 제약과 Zig 0.17.0 의존성을 놓치기 쉬워요.

이번 기록은 Rust와 Zig 중 어느 언어가 항상 낫다는 결론을 주지 않아요. 컴파일러처럼 자료구조와 할당 방식을 강하게 통제하는 프로젝트에서 Zig가 어떤 이점을 줬고, 그 대가로 어떤 안전장치와 개발 편의를 잃었는지 실제 수치로 보여 줘요. 0.1.0이 나온 뒤에는 안정판 도구 체인에서도 빌드 속도와 정확성, 유지보수성이 같은 수준으로 이어지는지 확인할 수 있을 거예요.

참고 자료

  1. Roc 컴파일러의 Rust→Zig 재작성 진행기 — GeekNews
  2. How Our Rust-to-Zig Rewrite is Going — Richard Feldman
728x90