Rust를 소스에서 빌드하는 과정이 왜 지나치게 무겁고, 의존성이 많으며, 바이너리 부트스트랩 컴파일러에 의존하는지에 대한 비판적 분석.
1 Feb 2026Michael Neumann Rust를 소스에서 빌드해 본 적이 있나요? 왜 그러냐고 묻고 싶을 수도 있습니다.
rustup으로 Rust를 사용할 수 없기 때문일 수 있습니다그렇게 어렵지는 않아야 하겠죠, 맞나요? 그런데... 이 글은 그 질문에 답하려고 하며, 왜 제가 _Rust는 부트스트래핑 과정을 크게 바꿔야 한다_고 생각하는지를 설명합니다. 자세한 내용으로 들어가기 전에, 다른 언어들과 비교했을 때 DragonFly BSD에서 Rust 1.81을 빌드하는 데 제게 얼마나 오래 걸렸는지 먼저 봅시다(도표는 R과 ggplot2 덕분입니다):
경고: 이것은 Rust에 대한 "불평"입니다! 언어 자체에 대한 것이 아닙니다. Rust는 아름답습니다. 문제는 현재 구현체입니다. 더 정확히 말하면, Rust의 공식 구현(gcc 버전이 아닌)의 _부트스트래핑 과정_과 Rust를 소스에서 빌드하는 데 필요한 엄청난 양의 _의존성_에 대한 불평입니다. 터미널에서 실행되는 것처럼 "미니멀리스트" 소프트웨어를 작성한다면, 이 "비대함"을 제발 인식하고 의존성이 덜 많고 자원 소모가 덜한 언어를 사용할 가능성을 고려해 주세요. 아니면 더 가벼운 "gcc" 버전의 Rust를 기다리세요. 감사합니다!
이 글에서 OCaml은 기준점 역할을 합니다. 더 나은 언어라서가 아닙니다. OCaml도 비교적 복잡하지만 부트스트랩 과정이 잘 구현되어 있습니다. 완전한 OCaml 툴체인은 단지 3분 만에 다음 명령으로 소스에서 살아나게 할 수 있습니다.
./configure && make && make
install
.
Rust의 README.md의 "Installing from Source"를 인용하면 다음과 같습니다.
정말로 소스에서 설치하고 싶다면(권장하지는 않지만), INSTALL.md를 보세요.
이 경고에도 불구하고, 저는 Rust 1.84.1의 소스 rustc-1.84.1-src.tar.gz를 다운로드합니다(위 그림에는 Rust 1.84.1을 빌드할 수 없었기 때문에 Rust 1.81.1의 빌드 시간을 보여준다는 점에 유의하세요). 압축된 tarball의 크기는 고작 699 MB입니다. 그런데, 이게 CD-ROM 한 장에 아직 들어갈까요? 아니요, 들어가지 않습니다!
비교를 위해 OCaml의 소스(버전 5.4.0)는 압축 시 6.0 MB입니다. 두 자릿수 자릿수 차이, 아니 두 자릿수 차수라고 외치고 싶어질 겁니다! 두 자릿수 차수...
공정하게 말하자면, 이 699 MB 분량의 소스 코드에는 Rust를 빌드하는 데 필요한 모든 소스가 들어 있습니다. 여기에는 약 1천만 줄의 LLVM 코드와 세 가지 다른 OpenSSL 버전도 포함됩니다.
음, 맞습니다. 모든 소스는 포함되어 있습니다. 하지만 바이너리 Rust 컴파일러(그리고 cargo 바이너리)는 포함되어 있지 않고, 이것들이야말로 소스에서 Rust를 빌드하는 데 꼭 필요합니다!!! 잠깐, 뭐라고요? 닭과 달걀 문제가 있는 것 아닌가요? 우리는 지금 Rust를 소스에서 빌드하려는 중인데요...!? 네, 그렇습니다. 이 얘기는 나중에 다시 하죠...
계속해서 이 크고 아름다운 tarball의 압축을 풉시다. 2분 14초 후에 tar xvzf rustc-1.84.1-src.tar.gz가 끝납니다. 참고로, 이는 같은 머신에서 전체 OCaml 툴체인을 빌드하는 데 걸리는 시간보다 대략 1분 더 빠른 정도입니다. 이제 압축 해제된 소스 트리는 1.9 GB입니다. 이건 이미 지구에 살고 있는 인도인 수보다 바이트가 더 많습니다.
계속해서 이번에는 방금 압축을 푼 소스 트리 안의 README.md를 다시 열어 봅시다. 여전히 _"Building From Source [...] is not recommended [...] see INSTALL.md"_라고 되어 있지만, 안타깝게도 거기서 가리키는 INSTALL.md는 tarball에 들어 있지 않았습니다. 뭐, 우리는 이미 1.9 GB어치의 소스 코드를 받았으니 설치 문서를 넣을 공간이 남아 있지 않았던 모양입니다. 우선순위이이이이! 소스에서 빌드하는 것은 권장하지 않는다고 이미 말했잖아요!
Rust를 빌드하기 전에(제가 최대한 미루고 싶은 일입니다), Rust 소스 트리에 cloc을 "빠르게" 돌려 봅시다...
빠르게라 함은:
... 아직도 파일 수를 세는 중입니다...
... 아직도 세는 중... 아아아안드... 아직도 세는 중...
... 1분이 넘었는데 351365개의 텍스트 파일이라고 뜹니다...
... 이제는 고유 파일을 세는 것 같습니다...
... 다시 2분이 지나자 276326개의 고유 파일이라고 나옵니다... 와우...
... 그리고 아직도 세고 있습니다. 뭘 세는지는 모르겠지만, 이번에는 줄 수일지도...
... 맙소사, 이제 CPU 팬이 최고 속도로 돕니다. 7분이 지났습니다...
... 공정하게 말하면, Perl(cloc)이 여기서는 최선의 선택이 아닐 수도 있겠네요...
... 그러려면 Rust로 구현된 tokei가 필요하겠죠...
... 이제 8분이 지났습니다...
... 더 오래 걸리면 전원 공급 장치를 연결해야 할지도 모르겠습니다...
... 9분(즉 OCaml의 3배)... 그리고 3-2-1...
... 마지막 제안입니다... 10분이면 끝납니다!
꽤 빨랐죠, 그렇지 않나요? 수많은 "Line count, exceeded timeout:" 줄과 81443 files ignored를 얻은 것을 제외하면, cloc 출력은 정말 인상적입니다.
files language blank comment code 68005 Rust 1251355 2413958 20734039 51146 C++1435178 2396405 7685767 71995 C 1083792 2936843 5724464 ............... 19 Pest 226 289 849 ............... 1 Brainfuck 3 4 10 1 sed 0 0 5 276326 SUM 6345959 10746776 50771605
정말 놀라운 언어 목록입니다! 지면에 맞추려고 출력에서 101줄을 잘라야 했습니다. 대체 "Pest"와 "SnakeMake"는 뭐죠??? 하하, 적어도 Brainfuck 10줄은 있네요!
좋아요, 그러면 주석과 빈 줄을 제외하고 총 2천만 줄의 Rust 코드가 68005개의 Rust 파일에 들어 있다는 거죠? 그리고 총합으로는 276326개 파일에 5천만 줄의 "코드"가 있고요. 인상적입니다! 게다가 여러 "timeout" 때문에 목록조차 완전하지 않습니다.
참고로, OCaml 5.4.0 툴체인의 코드 줄 수도 빠르게 세어 봅시다. 빠르게라 함은 3초라는 뜻입니다.
files language blank comment code 2825 OCaml 57092 106578 366954 307 C 8478 10140 47737 70 Bourne Shell 5718 6223 35216 12 m4 1481 108 12340 79 C/C++ Header 1747 3203 5084 12 Assembly 548 2169 4828 22 make 916 576 3463 25 Markdown 622 34 1849 12 AsciiDoc 518 0 1682 ............... 1 C#2 0 9 3433 SUM 78065 130234 487018
(출력에서 19줄을 제거했습니다).
즉 총 50만 줄의 코드입니다. "Pest"도 없고, SnakeMake도 없고, Brainfuck도 0줄입니다. 그것도 충분히 나쁜데 C# 9줄이 들어 있네요. 그건 틀림없이 오류일 겁니다:).
바이트코드 인터프리터와 5개 플랫폼(x86-64, arm64, RISC-V, s390x, powerpc)을 위한 네이티브 코드 생성기를 함께 제공하는 고급 언어인 OCaml에 대해 반백만 줄의 코드라면 그렇게 나쁘지 않습니다. 게다가 OCaml은 C 컴파일러와 gmake, m4 같은 표준 빌드 도구만으로 별다른 설정 없이 빌드된다는 점도 주목할 만합니다.

이제 Rust를 컴파일해 봅시다! Rust 1.84.1은 빌드되지 않았기 때문에(기억이 맞다면 빌드 시작 후 2시간쯤 지나서 어떤 오류 메시지가 났습니다), 대신 Rust 1.81을 빌드해 보았습니다.
여기 Rust 1.81의 빌드 시간입니다. 앉아서 보세요:
12563초, 즉 3시간 30분입니다.
OCaml은 197초, 즉 3분 17초 만에 빌드됩니다. 물론 같은 머신에서 말입니다.
이는 OCaml보다 63배 느리고, Python보다 162배 느리며, Lua보다 4753배 느립니다.
공정하게 말하자면 Rust의 빌드 시간에는 LLVM, cargo 그리고 몇몇 다른 도구를 빌드하는 시간도 포함되어 있고, Rust 자체도 적어도 두 번 빌드합니다. stage1은 Rust 1.80 부트스트랩으로 빌드한 Rust 1.81 컴파일러이고, stage2는 stage1(1.81)을 사용해 다시 자기 자신을 빌드합니다.
DragonFly BSD에서는 github의 우리 자체 부트스트래핑 저장소를 사용합니다. 적절한 버전의 cargo, 부트스트랩 rustc, Python이 설치되어 있다면, 다음과 같은 명령으로 Rust를 부트스트랩할 수 있을 수도 있고 없을 수도 있습니다.
export LIBSSH2_NO_PKG_CONFIG=1
export LIBGIT2_NO_PKG_CONFIG=1
export LIBCURL_NO_PKG_CONFIG=1
export LIBZ_NO_PKG_CONFIG=1
export LIBLZMA_NO_PKG_CONFIG=1
export PROFILE=release
export LIBZ_SYS_STATIC=1
export OPENSSL_NO_PKG_CONFIG=1
./configure \
--release-channel=stable \
--enable-cargo-native-static \
--enable-extended \
--enable-vendor \
--enable-locked-deps \
--local-rust-root=/path/to/bootstrap/compiler \
--sysconfdir=/opt/rust/etc \
--prefix=/opt/rust \
--python=python \
--disable-llvm-static-stdcpp \
--disable-docs
python x.py build --config ./config.toml
python x.py dist --config ./config.toml
python x.py install --config ./config.toml
빌드에 3시간 30분이 걸린다는 것뿐만 아니라, 또 다른 문제가 있습니다.
현재 Rust에는 Rust 이외의 다른 언어로 작성된 부트스트랩 컴파일러가 없기 때문에, Rust 컴파일러를 컴파일하려면 Rust 컴파일러가 필요하고... Rust 컴파일러를 컴파일하려면 Rust 컴파일러가 필요하고... Rust 컴파일러를 컴파일하려면 Rust 컴파일러가 필요하고... 그러다... 이 시점에서 스택 오버플로가 발생해 우리의 아름다운 무한 재귀를 멈춥니다.
공정하게 말하자면 이것은 진정한 무한 재귀는 아닙니다. 예전에는 OCaml로 작성된 Rust 컴파일러가 있었기 때문입니다. 하지만 그건 10년도 더 전의 일로, 제가 2013년에 Rust를 사용하기 시작하기 훨씬 전입니다.
정말로 그 초기 Rust 버전부터 시작해서, 컴파일러가 OCaml로 작성되어 있던 그 시점에서부터 현재 버전에 도달할 때까지 Rust의 각 버전을 하나씩 차례로 컴파일하고 싶다고 상상해 봅시다. 이론적으로는 가능합니다. 제 대략적인 추정으로는 중간 단계의 Rust 컴파일러를 백 개쯤 컴파일해야 할 것이고, 그러면 강력한 빌드 머신 하나를 24시간 내내 10일 이상 붙잡아 두게 될 겁니다. 중간에 실패해서 버그를 고치고 패치를 적용하는 시간은 제외하고요. 비현실적입니다.
문제의 중요한 부분이자 Rust 부트스트래핑이 그렇게 비싼 이유는, Rust의 각 버전이 정확히 이전 버전으로 부트스트랩되어야 한다는 점입니다.
지금은 더 이상 그렇지 않을 수도 있지만, 예전에는 확실히 그랬습니다. Rust 버전 n+1을 빌드하려면 Rust 버전 n이 필요했습니다.
이쯤에서 다른 언어들이 부트스트랩 과정을 어떻게 처리하는지 살펴보는 것이 도움이 될 수 있습니다.
_Zig 언어_는 somehow 130만 줄짜리 ANSI C 파일 zig1.c를 배포하는 데 성공했습니다. 이 파일에는 Zig 컴파일러와 LLVM이 들어 있습니다. 제 생각에는 요즘은 Zig로 작성된 Zig 컴파일러를 WASM으로 컴파일한 다음 거기서 다시 C로 변환하는 것 같습니다. 영리하죠. 다만 그 파일을 vim으로 열려고 하지는 마세요. LLVM을 제거하게 되면 비대함의 상당 부분이 사라질 겁니다.
반면 _OCaml_은 C로 구현된 바이트코드 인터프리터(ocamlrun)를 함께 제공합니다. 덕분에 3.4M 크기의 이식 가능한 부트스트랩 컴파일러를 바이트코드 바이너리로 배포할 수 있고(boot/ocamlc 참조), 이를 이용해 OCaml 컴파일러를 부트스트랩합니다. 이 과정은 OCaml의 BOOTSTRAP.adoc에 잘 문서화되어 있습니다.
_Go 언어_의 컴파일러 역시 Go 자체로 작성되어 있지만, Go는 다른 Go 컴파일러를 부트스트랩하는 데 필요한 Go 컴파일러 버전에 대해 덜 엄격합니다. 예를 들어 Go 1.24와 1.25는 Go 1.22 컴파일러를 요구합니다. 게다가 Go 1.4는 C로 작성되었기 때문에, 그것을 이용해 더 새로운 Go 컴파일러를 부트스트랩할 수 있습니다. 더 중요한 점은 Go는 약 3분 안에 컴파일된다는 것입니다. 따라서 뭔가가 깨져서 수정해야 할 때 훨씬 수월합니다.
Python, Ruby, Lua 같은 언어들은 모두 C로 구현되어 있으므로 스스로를 부트스트랩할 필요가 없습니다.
Erlang/OTP 런타임 시스템 erts는 C/C++로 구현되어 있고, BEAM 바이트코드를 실행하기 위해 바이트코드 인터프리터(및 JIT)를 사용합니다. Erlang/OTP 소스를 다운로드하면 Erlang 컴파일러와 Erlang 부트스트랩에 필요한 모든 것에 대한 미리 컴파일된 바이트코드가 포함되어 있습니다.
_Erlixir_는 Erlang/OTP 플랫폼 위에서 실행되는 언어로, 조금 다릅니다. 그 컴파일러는 Elixir가 아니라 Erlang으로 구현되어 있습니다. 따라서 Erlang/OTP만 설치되어 있으면 Elixir를 부트스트랩할 준비가 끝납니다.
그렇다면 어떤 이유로든 당신의 플랫폼에서 사용할 수 있는 바이너리 Rust 부트스트랩 컴파일러가 없다면 어떻게 될까요?
그 경우에는 다른 시스템에서 실행되는 기존 바이너리 부트스트랩 컴파일러를 사용해 Rust 컴파일러 rustc를 크로스 컴파일해야 합니다. 저는 DragonFly용으로 10여 년 전에 한 번 해 본 적이 있습니다. 딱히 즐거운 일은 아닙니다.
여기서 중요한 점은, 당신의 시스템용 Rust를 크로스 컴파일하려면 여전히 다른 플랫폼용 바이너리 부트스트랩 컴파일러가 필요하다는 것입니다. 현재로서는 이를 현실적으로 피할 방법이 전혀 없습니다.
요약하자면, 제 최선의 지식으로는 기존 바이너리 Rust 부트스트랩 컴파일러를 다운로드하지 않고 공식 Rust 배포판을 컴파일하는 것은 거의 불가능합니다. 아니라면 기꺼이 듣고 싶습니다.
개인적으로 저는 이 상황이 그리 걱정되지는 않습니다. 하지만 제가 러시아, 중국, 북한, 이란의 소프트웨어 개발자라면, 원격의 미국 서버에서 바이너리 Rust 컴파일러를 다운로드해야 한다는 점이 조금 더 신경 쓰였을 것 같습니다. 무슨 일이 잘못될 수 있겠습니까?
크기 관점에서 보면 버전 1.84.1의 "공식" Rust 구현은 꽤 "비대"합니다.
이를 OCaml 버전 5.4.0과 비교해 봅시다.
게다가 Rust는 바이너리 부트스트랩 컴파일러 없이는 빌드할 수 없습니다. 저는 Rust용 GCC Front-End가 더 성숙해지면 이것이 바뀌기를 바랍니다.
_Rust가 의존하는 대량의 의존성_과 _바이너리 부트스트랩 문제_는, 제 의견으로는, 이상적으로 가능한 한 의존성이 적어야 하는 시스템 프로그래밍 작업에서 Rust를 첫 번째 선택지로 만들지 못합니다. 다른 더 복잡한 것들이 그 위에 세워질 기본 도구를 개발하고 있다면, 그 기반이 그 위에 세워질 것보다 천 배 더 복잡해서는 안 됩니다. 동의하시나요?
반면 대규모 엔터프라이즈 애플리케이션이나 독점적 임베디드 및 안전 필수 제품을 만들고 있다면 이 문제는 훨씬 덜 심각합니다. 만약 당신이 이런 업계에 있다면, Rust crate에 대한 공급망 공격을 항상 의식하고 절대로 무심코 cargo update를 실행하지 마세요! 당신의 소프트웨어가 아마 의존하게 될 수백 개의 crate 변경 사항을 추적하는 데 행운을 빕니다.
개인적으로 저는 정확히 다음을 하기 위해 699 MB짜리 압축 tarball을 내려받고, 이를 1.9 GB로 압축 해제한 뒤, 그 다음 세 시간 반 동안 Rust를 컴파일해야 한다는 점에 별로 설득되지 않습니다.
cat 또는 다른 "core-utils"제 조언은 이렇습니다. 단지 지금 인기가 많다는 이유만으로 무턱대고 모든 것에 Rust를 쓰지 마세요. 현재 당면한 작업에 Rust가 정말 적절한 도구인지 신중히 생각해 보세요. 좋은 대안은 많습니다.
안녕히 주무세요.