요약
Rust 컴파일러에서 10년 가까이 이어진 장기 과제들이 2026년에 한꺼번에 결실을 맺고 있습니다.
Rust 컴파일러의 성능 변화를 분류하는 작업(perf triage)에 참여해 온 개발자 마티아시 라체크의 글입니다. 올해 유독 일이 많았던 것이 아니라, 오래 다져 온 기반 작업이 동시에 마무리 단계에 들어섰다고 봤습니다.
왜 중요한가
- 차세대 트레이트 솔버와 새 빌림 검사기 Polonius처럼 수년간 기다린 작업이 끝나 막혀 있던 기능이 풀립니다.
- 컴파일 시간과 빌드 결과물 용량처럼 Rust 사용자가 매일 겪는 불편을 여러 갈래에서 동시에 줄이고 있습니다.
- 프로젝트 목표 제도와 새 후원 프로그램이 자리 잡아 장기 작업을 이어 갈 기반이 생겼습니다.
핵심 내용
- 10년 가까이 만든 차세대 트레이트 솔버(타입 규칙을 따지는 핵심 부품)가 건전성 버그와 async의 한계를 해결합니다.
- 메타데이터를 따로 넣지 않는 설정이 기본값이 되어 target 디렉터리(빌드 결과물 폴더)가 5~30% 줄었습니다.
- 병렬 프런트엔드(컴파일 앞 단계를 여러 스레드로 나눠 처리하는 기능)가 안정화를 앞두고 있습니다.
- 업그레이드 때마다 컴파일 속도를 깎곤 하던 LLVM이 23 버전에서는 거의 모든 지표를 끌어올렸습니다.
- 필자는 지쳐 있던 웹 일을 정리하고 컴파일러 성능 작업에 전념하려 GitHub Sponsors를 열었습니다.
HN 반응
- 일부 이용자는 새 솔버를 수년간 기다렸다며 오래 멈춰 있던 특수화(specialization) 기능까지 풀리길 기대했습니다.
- target 디렉터리 축소를 반기는 댓글이 많았고, 느린 빌드가 Rust 탓인지 코드 품질 탓인지를 두고 논쟁이 이어졌습니다.
> 이 작업이 컴파일러에 얼마나 큰 의미인지는 아무리 강조해도 모자랍니다. 컴파일러 개발자들이 수년간 꿈꿔 온 굵직한 기술 부채 하나가 해결되는 겁니다.
정말 그래요. 저는 새 솔버를 거의 5년째 기다리고 있었거든요. 이걸로, 그리고 Polonius까지 나오면 몇 년째 연옥에 갇혀 있는 specialization도 풀렸으면 좋겠네요.
specialization도 움직임이 좀 있습니다. https://goals.rust-lang.org/2026/specialization.html
새 솔버에 직접 막혀 있는 건 아니지만, 타입 시스템이 더 나은 모양새가 되면 관련된 건전성 문제를 따져 보기가 확실히 수월해집니다.
좀 더 정확히 말하면, 새 솔버에 막혀 있다고 알려져 있지 않았던 겁니다(다른 몇몇 기능과는 달랐죠). 최근까지 아무도 손댈 엄두를 못 냈으니까요... (진지하게 말하면 투자가 충분하지 않았습니다).
저는 이제 러스트가 미래라고 생각하지 않습니다. 컴파일러에 몇 번 기여했고 몇 년간 꽤 깊이 관여했는데도요. 러스트는 너무 널리 퍼졌고, 그 탓에 컴파일 시간이 너무 길어졌습니다.
고(Go)의 지루함, 지그(Zig)의 컴파일 속도와 어쩌면 컴파일 타임 기능, 그리고 러스트의 안전성을 갖춘 더 새로운 언어가 나올 것 같습니다. 문법은 아마 리스프 같은 식이겠죠.
https://xkcd.com/927/
그 말은 성공작으로 인정받으려면 아직 못 넘은 어떤 기준선이 있다는 뜻이잖아요. 그런데 러스트는 이미 어디에나 있어요. 리눅스 커널에도 들어갔고, 심지어 윈도에도 들어갔다고요(!).
2026년은 정말 러스트의 해였죠. 브라우저에서도, 네이티브로도 돌아가니까 일렉트론 킬러가 됐고요.
참 이상한 생각이네요. 처음엔 기능이 더 풍부하고 타입 시스템이 더 엄격한 언어를 예측하시는 줄 알았습니다.
이제 Go의 "지루함"이나 사람 위주의 사용 편의성은 필요 없습니다. 컴파일이 빠른 건 여전히 좋지만요.
리스프 문법도 AI 시대에는 큰 가치가 없다고 생각합니다. 리스프는 파싱하기 쉽고 인터프리터를 건드리기 쉽게 최적화된 언어 같습니다. 사람이 읽기 좋게 만들려고 설계한 적은 없었다고 봅니다. 다만 리스프는 균일하고 최소한의 문법이라 배우기는 쉽습니다. -- 잘 모르겠네요, 제가 리스프 문법의 가치와 유연성을 과소평가하는 걸 수도 있습니다.
지금 프로그래밍 언어에서 가장 가치 있는 특성은 사람이 읽기 쉬운 가독성, 그리고 사람이 믿고 기댈 수 있는 보장을 주는 타입 시스템이라고 생각합니다. AI가 쓴 코드 중 가장 흥미진진했던 일부는 Lean으로 작성된 것이었고요.
target 디렉터리 크기가 줄어들면 정말 살겠어요. 당신도, 러스트도 잘되길 바랍니다!
이거예요!
여기서나 다른 SNS에서나 러스트는 컴파일이 느리다는 밈이 계속 도는데, 러스트로 일해 본 사람이라면 실제로는 컴파일러 속도보다 target 디렉터리가 훨씬 더 짜증 나는 잔 상처라는 걸 압니다(가장 흔한 작업 흐름에서는 컴파일러 속도가 다른 툴체인보다 눈에 띄게 나쁘지도 않고요).
이걸 잔 상처라고 부르다니 말도 안 됩니다. 저는 Codex CLI를 포크해 본 적이 있는데, 채팅과 HTTP 요청을 관리하는 터미널 UI일 뿐인데도 최상급 CPU에 증분 빌드와 캐시 최적화를 전부 켜 놓고도 사소한 변경 하나를 다시 빌드하는 데 15분이 넘게 걸리고, 빌드 캐시가 수십 GB나 쌓였습니다. 둘 다 용납할 수 없습니다. 더 할 말 없어요. Codex CLI가 바이브 코딩으로 만든 괴물이라는 점이 문제의 일부라는 건 압니다(놀랍게도 소프트웨어는 아직 정복된 게 아니죠). 하지만 Go로 만든 바이브 코딩 괴물에서는 이런 문제를 겪어 본 적이 없습니다. 언어에 더 높은 기준을 요구하세요. 러스트를 버리자는 말이 아닙니다. "ㅋㅋ 이 정도면 괜찮지, 이걸 어떻게 해볼 방법이 있겠어" 하며 어깨나 으쓱하지 말고, 러스트 툴체인을 Go 툴체인보다 낫게 만들려고 애쓰자는 겁니다.
Codex CLI의 코드 품질은 모르겠지만, 게임 엔진 전체(Bevy)를 빌드하는 데 걸리는 시간이 그보다 훨씬 훨씬 짧습니다.
이런 이야기를 들으면 환경 설정에 문제가 있는 것 같습니다. 빌드 디렉터리에 Windows Defender 같은 걸 켜 두신 건 아닙니까?
Bevy는 의도적으로 수많은 크레이트로 쪼개 놓았고, 규모도 작고, 빌드 시간 최적화에 매달리는 개발자가 있습니다.
그건 "러스트 컴파일이 빠르다"가 아니라 "러스트는 코드를 특정한 방식으로 설계하라고 요구한다"의 또 다른 사례예요.
그렇다면 처음 든 예시도 "러스트 컴파일은 극단적으로 느리다"가 아니라(느린 건 맞지만), "바이브 코딩으로 만든 소프트웨어는 품질이 나쁘다"의 또 다른 사례겠죠.
맞는 말이긴 한데, 러스트는 다른 어떤 언어도 못 주는 안전성과 효율의 조합을 줍니다.
이상한 소리를 하시네요. 저는 대형 러스트 프로젝트에서 5년 가까이 일했습니다(한 번은 러스트로 짠 블록체인 전체였어요). 느립니다. 더 할 말 없어요. CI 시간을 줄이라는 압박에 늘 시달렸고, 몇 분 아끼려고 별의별 기상천외한 최적화까지 했습니다. 반면 같은 시기에 제가 Go로 짠 플랫폼 코드는 CI 시간이 몇 분을 넘은 적이 거의 없었어요.
어느 한쪽이 더 낫다는 뜻은 아닙니다. 다만 러스트 컴파일이 느리다는 경험을 말한 사람에게 그건 당신 환경 탓이라고 하는 건 좀 이상합니다.
느린 건 맞습니다. 그걸 부정할 사람은 없을 겁니다. 컴파일 시간이 끔찍해지지 않으려면 노력이 필요하다는 것도 사실이고요.
하지만 15분이라니요? rustc조차 (고사양 컴퓨터에서는) 그렇게 오래 걸리지 않습니다. 그것도 적어도 두 번 컴파일되는(부트스트래핑) 아주 큰 프로그램인데도요! 그건 그냥 엔지니어링이 형편없는 겁니다. 컴파일이 빠른 러스트 코드를 쓸 줄 모르는 거죠.
rustc를 대형 애플리케이션과 비교하면 안 됩니다. 큰 프로그램이긴 하지만 Codex CLI에 비하면 한참 작아요. Codex CLI는 크레이트가 1420개입니다.
방금 Codex CLI 바이너리와 code mode host만 "lto = false"로 릴리스 빌드를 해 봤는데 75분이 걸렸고, 그중 codex 바이너리의 마지막 rustc 단계만 1063초였습니다. Windows Defender는 껐고요. 2021년형 4코어 인텔 노트북에 RAM 32GB, SSD 0.5TB 환경입니다. 기본 동시성으로 돌리면 rustc 때문에 메모리가 부족해 머신이 뻗어서 cargo jobs = 4로 돌린 결과예요. 어쨌든 마지막 rustc 호출은 그 설정과 무관하고, lto=false인데도 1063초였습니다. 같은 CLI를 기본 thinlto로 빌드했을 때는 90분이었고요.
Codex가 어떻게 하면 컴파일이 빠른 러스트 코드를 쓸 수 있는지 말씀해 보시죠?? 어디 말해 보세요!
동작 면에서 보면 rustc가 Codex CLI보다 훨씬 많은 일을 합니다. Codex의 코드는 그냥 품질이 극도로 나쁩니다. 슬쩍 보니 HTTP 서버 라이브러리를 적어도 두 개(axum과 tiny_http)나 쓰는 것 같습니다! 빠르게 확인해 보니 rustc에는 (버전이 다른) 중복 전이 의존성이 44개인데 Codex에는 109개나 있어요! 게다가 의존성 2개는 다섯 번씩 중복됩니다(rustc에서 가장 많이 중복된 건 세 번뿐입니다)!
사람이 하는 대로 하면 되지 않을까요? 제네릭을 극단적으로 쓰지 않고, 필요 없는 의존성은 피하고, 중복 코드가 생기지 않게 의존성 버전을 맞추는 거죠. 등등.
그리고 리눅스는 (파일 시스템 성능 때문에, Dev Drive를 써도) 윈도보다 컴파일 시간이 (종종 상당히) 짧습니다.
어떤 러스트 프로젝트였습니까? 의존성은 몇 개였고요? 매크로를 쓰나요? 제네릭과 트레이트는요?
러스트 생태계 대부분은 컴파일 타임 트레이트와 단형화되는 제네릭(Go 제네릭이나 C++ 템플릿 같은 것)을 불필요하게 많이 씁니다. 언어 문법도 그렇게 쓰도록 부추기고요. 언어, 표준 라이브러리, 그리고 거의 모든 라이브러리 개발자가 정적 디스패치와 단형화 대신 동적 디스패치를 우선시하지 않는 한, 컴파일 시간은 절대로 Go 수준까지 내려가지 않습니다. 그게 성능과 RAM 사용량에서 얻는 이득의 대가입니다. 템플릿을 많이 쓰는 C++도 같은 이유로 같은 문제를 겪습니다.
Go가 컴파일이 빠른 건 인터페이스 정의가 런타임에 동적으로 디스패치되기 때문이고(러스트의
&dyn Trait이나 C++의 가상 클래스 같은 것), 오랫동안 제네릭이 없어서 슬라이스, 맵, 벡터 같은 타입을 마법 같은 특수 타입으로 만들 수밖에 없었기 때문입니다.컴파일이 빨라야 한다면 프로그램을 여러 바이너리로 쪼개거나, 거의 동적 디스패치만 쓰는 백엔드 생태계를 직접 만들어야 합니다. Cargo 의존성 트리가 크레이트를 50개 넘게 끌어오면 빠른 컴파일은 못 얻습니다. derive 매크로는 역병처럼 피해야 하고요. derive 매크로 대신 Cargo가 아닌 빌드 시스템이나
build.rs로 러스트 코드를 생성해야 합니다.Go와 달리 러스트는 메모리가 극도로 제한된 기기(마이크로컨트롤러, UEFI/BIOS 구현 등)도 겨냥하는데, 그런 곳에서는 단형화 덕에 메모리를 아끼고 최적화를 더 하고 CPU 사이클과 명령어 수를 줄일 수 있습니다. 러스트는 무엇보다 시스템 프로그래밍 언어입니다. 시스템 프로그래밍 언어에서는 이런 선택을 개발자가 책임져야 하고요. 러스트에 백엔드 라이브러리 생태계가 활발한 건 언어의 안전성 덕분에 시스템 개발자가 아닌 사람들도 올바르고 성능 좋은 프로그램을 빠르게 쓸 수 있게 되었기 때문일 뿐입니다.
약간 곁가지 얘기이긴 한데, Go 제네릭은 단형화되지 않습니다. 각 인스턴스는 GC shape 단위로 "스텐실"되고, 같은 shape인 타입들 사이의 차이는 "딕셔너리"로 처리하는데, 이건 Swift의 witness table이나 Go 자체의 인터페이스 vtable과 비슷한 방식입니다. 자세한 설명은 여기 있습니다. https://github.com/golang/proposal/blob/master/design/generics-implementation-dictionaries-go1.18.md
결과적으로 생성되는 코드와 런타임 간접 참조의 정도는 대개 단형화와 타입 소거의 중간쯤에 놓입니다.
저도 그랬지만, (성능 좋은 머신에서) 빌드 시간이 10분이 넘는 건 한 번도 본 적이 없다고 자신 있게 말할 수 있습니다.
저는 여러 조직에서 컴파일 시간 때문에 길어진 장애를 세 번 봤습니다. 이 경우들에서는 컴파일 시간이 RTO를 못 맞춘 주된 원인이었죠. 컴파일 시간은 리스크입니다.
제가 본 어떤 제안은 멀쩡한 모놀리스를 컴파일 시간 단축을 위해 마이크로서비스 십여 개로 쪼개자는 것이었어요(누적 CI 비용이 늘어나는 대가를 치르고요).
대안이 시스템 전체를 대대적으로 재설계하는 거라면 Zig와 Go가 정말 좋아 보이기 시작합니다.
그 제안은 아마 크레이트로 쪼개자는 거였을 겁니다. 크레이트는 러스트의 컴파일 단위라서 병렬화할 수 있어요. 마이크로서비스가 아니라 그냥 라이브러리이고, 결국 합쳐져 바이너리 하나가 됩니다.
그래도 Zig는 코어를 덤프하고 Go는 더 느린 데다 메모리도 더 먹는데, 그것도 중요하죠. (러스트 빌드 시간이 그런 걸 피하는 대가라는 얘기는 아니고, 처음부터 빠른 빌드를 목표로 삼지 않은 결과에 가깝습니다.)
저는 2015년부터 2023년까지 2012년형 60만 원짜리 Clevo 노트북으로 러스트를 전업으로 썼고, 코드 10만~60만 줄짜리 프로젝트에서 증분 빌드가 십몇 초대인 데 익숙했습니다. (처음부터 하는 릴리스 빌드는 지독하게 느렸지만, 그건 러스트를 업데이트할 때 한 번뿐이었어요.) 그래서 어떻게 이렇게까지 나쁠 수 있는지 도무지 모르겠습니다.
빌드 디렉터리를 러스트 업데이트당 한 번만 비운다고요? 나이틀리를 쓰지 않는 한 그게 가능하다는 게 이해가 안 갑니다. 저는 1TB에 여유도 넉넉한 디스크를 쓰는데도 정기적으로 비워야 해요(아주 자주는 아니지만, 릴리스 한 번에 한 번보다는 많이요).
둘 다(그리고 메모리도) 문제입니다. 그래도 빌드 용량은 디스크를 더 붙이면 훨씬 쉽게 해결되죠(용량이 꽉 묶인 맥을 쓰는 게 아니라면요...).
돈을 안 쓰고 해결하는 부분적인 방법이 있습니다.
Cargo.toml에debuginfo = "line-tables-only"를 넣어 보세요.