러스트에 대한 비판적 고찰: 장점과 트레이드오프
최종 수정일:
※ 면책 조항: 이 글은 특정 프로그래밍 언어의 설계 철학과 현실적인 트레이드오프에 대한 필자 개인의 비판적 분석을 담고 있습니다. 기술에 대한 평가는 다양한 관점에서 이루어질 수 있으며, 이 글은 그중 하나의 시각임을 밝힙니다.
러스트는 강력한 커뮤니티와 마케팅을 통해 특정 인식을 형성하고 있으며, 이는 때로 객관적인 장단점 평가에 영향을 주기도 합니다. 이 글에서는 러스트의 핵심적인 특징들을 비판적인 관점에서 재검토하고자 합니다.
Zero-Cost Abstractions: 마케팅 용어에 가까운가?
C 언어도 ‘비용 없는 추상화(Zero-Cost Abstractions)’를 효율적으로 지원합니다. 사실, C++ 언어만큼 비용 효율적인 추상화를 제공하는 언어는 드뭅니다. 그런데 러스트는 이 개념을 전면에 내세우며 홍보하는 경향이 있습니다. 이는 러스트의 장점을 설명하는 핵심 논리 중 하나지만, 마케팅적으로 강조된 구호라는 시각도 존재합니다.
러스트가 말하는 ‘비용 없는 추상화’는 소유권 시스템과 빌림 검사기를 통해 런타임 오버헤드를 줄인다는 개념입니다. 하지만 이는 개발자가 컴파일러의 엄격한 규칙을 학습하고 따라야 하는 비용을 수반합니다. C++ 언어에서는 개발자가 직접 제어하며 효율을 극대화할 수 있는 영역을, 러스트는 언어 차원에서 강제하며 초보자에게는 높은 학습 곡선을 제시합니다.
결론적으로 러스트의 ‘비용 없는 추상화’는 C++ 언어가 이미 제공하는 것을 다른 방식으로, 그리고 더 많은 제약을 가하며 재해석한 것으로 볼 수 있습니다. 이를 러스트만의 특별한 장점인 것처럼 강조하는 것은, 기존 개념에 새로운 의미를 부여하여 ‘최신 기술’이라는 인식을 강화하는 전략으로 비칠 수 있습니다. 진정한 비용 효율은 개발자의 역량과 유연한 제어에서 나오는 것이지, 언어의 엄격한 강제성만으로 달성되는 것은 아닙니다.
러스트는 Segmentation Fault 가 없다?
러스트로 작성된 애플리케이션은 세그멘테이션 폴트(Segmentation Fault)가 발생하지 않는다고 알려져 있습니다. 이는 러스트가 메모리 안전성을 강력하게 보장하기 때문입니다. 하지만 세그멘테이션 폴트가 발생하지 않는 대신, 프로그램은 panic
을 일으키며 종료될 수 있습니다.
물론, C 언어에서 세그멘테이션 폴트가 발생하지 않고도 프로그램이 오작동하여 심각한 보안 취약점으로 이어지는 최악의 경우와 비교했을 때, 러스트의 panic
은 분명한 안전성 향상으로 볼 수 있습니다. panic
은 예측 불가능한 메모리 오류 대신 통제된 방식으로 프로그램이 종료되도록 유도하기 때문이죠.
그러나 ‘러스트는 세그멘테이션 폴트가 없다’는 구호는 자칫 프로그램이 어떠한 오류로도 종료되지 않는다는 오해를 낳을 수 있습니다. 최종 사용자 입장에서 보면, 프로그램이 세그멘테이션 폴트로 갑자기 꺼지든, panic
으로 인해 종료되든 결과는 동일하게 애플리케이션의 중단입니다. 러스트가 특정 유형의 메모리 오류를 막을지라도, 프로그램이 완벽하게 ‘죽지 않는’다는 인식을 주는 것은 경계해야 합니다.
러스트 패닉: 오류 처리의 허점인가, 철학인가?
공식 예제 소스코드를 컴파일해봤습니다.
https://doc.rust-lang.org/book/ch12-01-accepting-command-line-arguments.html
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let query = &args[1];
let file_path = &args[2];
println!("Searching for {query}");
println!("In file {file_path}");
}
인자 없이 실행하면 이렇게 종료됩니다.
$ ./main
thread 'main' panicked at main.rs:6:22:
index out of bounds: the len is 1 but the index is 1
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
제시된 코드 예시에서 러스트 프로그램이 인자 부족으로 인해 panic
을 일으키며 종료되는 것은 러스트의 예상된 동작입니다. 러스트 커뮤니티에서는 이를 미정의 동작(undefined behavior)으로 이어지지 않도록 프로그램을 안전하게 중단시키는 메커니즘으로 설명합니다. 이는 C/C++에서 흔히 발생하는 예측 불가능한 메모리 오류나 보안 취약점을 방지한다는 러스트의 핵심 철학과 연결됩니다.
그러나 러스트가 특정 종류의 오류를 컴파일 타임에 잡아낸다고는 하나, 실제 애플리케이션 운영 환경에서는 예기치 않은 입력이나 외부 환경 변화로 인한 panic
이 발생할 수 있습니다.
러스트에서도 프로그래머가 panic
처리나 Result
타입의 적절한 활용에 소홀하면 애플리케이션은 여전히 갑작스럽게 종료될 수 있습니다. 러스트의 ‘안전성’이 특정 메모리 오류의 영역에 강점을 보이는 것은 분명하지만, 프로그램의 전반적인 강건성(robustness), 즉 어떤 상황에서도 안정적으로 동작하며 오류를 복구하는 능력까지 완벽하게 보장하는 것은 아닙니다.
결국, 러스트의 panic
은 위험한 오류를 명확히 드러내고 디버깅을 돕는다는 긍정적인 측면이 있습니다. 그러나 최종 사용자에게는 세그멘테이션 폴트와 마찬가지로 갑작스러운 프로그램 종료라는 동일한 결과를 안겨주는 점은 변하지 않습니다. 러스트가 언어 차원에서 안정성을 ‘강제’하는 것은 분명하지만, 이것이 프로그래머의 모든 책임을 면제해주거나 모든 종류의 런타임 오류를 없애주는 것은 아니라는 점을 명확히 인지해야 합니다.
러스트 Result
에 대한 비판적 평가: 명확성이 곧 불편함인가?
러스트의 Result
타입은 아래 공식 문서들에서 자세히 설명하고 있습니다.
https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
https://doc.rust-lang.org/std/result/
러스트의 Result
타입은 공식 문서에서 설명하듯, 오류 처리를 명확하게 강제하는 강력한 도구입니다. 하지만 이 강제성은 개발자의 편의성과 생산성을 담보로 한, 양날의 검과도 같습니다. Result
는 기존 언어들의 오류 처리 방식이 가진 문제점을 해결하려 했지만, 그 과정에서 새로운 형태의 ‘비용’, 즉 개발 흐름의 불편함을 만들어냈다는 비판에서 자유로울 수 없습니다.
예를 들어, 자바스크립트나 파이썬의 try...catch
구문은 주 로직과 오류 처리 로직을 분리하여 코드의 가독성을 높이고, 개발자가 핵심 로직에 집중하게 돕는다는 명백한 장점이 있습니다. 하지만 이런 구조는 종종 처리되지 않은 예외(unchecked exception)가 런타임에 어디서 터져 나올지 모르는 불안정성을 내포하기도 합니다.
C 언어에서 흔히 쓰이는 errno
전역 변수 방식은 그 간결함에 있어서는 타의 추종을 불허합니다. 별도의 복잡한 오류 전파 로직 없이 몇 줄의 코드로 오류를 확인하고 처리할 수 있습니다.
const char *err_msg = strerror (errno);
printf ("failed: %s\n", err_msg);
바로 이 지점에서 러스트의 Result
는 탄생했습니다. Result
는 errno
와 try...catch
의 근본적인 문제점들, 즉 ‘잊힐 수 있는 오류’와 ‘숨겨진 제어 흐름’을 언어와 컴파일러 차원에서 원천적으로 해결하려는 시도입니다. 모든 오류 가능성을 타입 시스템에 명시하고, 컴파일러가 그 처리를 강제함으로써 프로그램의 신뢰성을 극단적으로 끌어올리는 것이죠.
그러나 이 해결책은 상당한 대가를 요구합니다. 모든 오류 가능성을 명시적으로 처리하도록 강제하는 것은, 필연적으로 더 많은 보일러플레이트 코드와 복잡한 매칭 패턴을 낳습니다. 이는 빠른 프로토타이핑이나 비즈니스 로직의 신속한 구현이 중요한 환경에서 개발 생산성을 현저히 저해하는 요소로 작용할 수 있습니다.
이러한 관점에서 볼 때, 러스트의 선택은 ‘안전’이라는 가치를 위해 개발 생산성의 ‘비용’을 기꺼이 지불하는 극단적인 트레이드오프로 보입니다. Result
가 제공하는 명시성과 안정성은 분명 강력한 장점이지만, 이는 간결함과 개발 속도를 중시하는 다른 언어들의 철학적 이점을 포기한 대가입니다. 이는 마치 모든 사소한 위험을 막기 위해 불필요할 정도로 복잡한 안전장치를 모든 길목에 설치하여, 결국 길을 가는 것 자체를 고통스럽게 만드는 과잉 교정(over-correction)처럼 느껴질 수 있습니다.
러스트 바이너리의 ‘비대함’: 원인, 증거, 그리고 반론의 진실
러스트로 애플리케이션을 만들다 보면 생성되는 바이너리의 크기에 대한 의문은 단순한 ‘증가’ 수준을 넘어설 때가 많습니다. 특히 C와 같은 전통적인 컴파일 언어와 비교할 때, 러스트 바이너리는 충격적일 만큼 ‘거대하다’는 인상을 지울 수 없습니다. 이 문제는 러스트의 특정 디자인 철학과 배포 방식에서 비롯된, 피하기 어려운 현실입니다.
1. 근본 원인: 불안정한 ABI와 사실상 강제된 정적 링킹
러스트로 무엇인가를 만들면 표준 라이브러리(libstd
)를 사용하게 마련입니다. 문제의 핵심은 이 libstd
가 현재 ABI(Application Binary Interface) 안정성을 전혀 보장하지 않는다는 점에 있습니다.
libstd
는 다음과 같이 버전 식별을 위한 해시값이 포함된 파일명으로 배포됩니다.
$ pkg list rust | grep libstd
...
/usr/local/lib/rustlib/x86_64-unknown-freebsd/lib/libstd-441959e578fbfa8d.so
...
이것이 왜 문제인지 “Hello World!” 앱을 동적 링킹하는 실험을 통해 명확히 알 수 있습니다.
fn main() {
println!("Hello World!");
}
이 코드를 동적 링킹 옵션과 함께 컴파일하고 strip
명령어로 불필요한 정보를 제거하면, 바이너리 크기는 5960바이트로 작아질 수 있습니다.
rustc -C opt-level=s -C prefer-dynamic -C target-feature=-crt-static hello.rs
하지만 ldd
명령으로 의존성을 확인하면 치명적인 문제가 드러납니다.
$ LD_LIBRARY_PATH=/usr/local/lib/rustlib/x86_64-unknown-freebsd/lib ldd ./hello
./hello:
libstd-441959e578fbfa8d.so => /usr/local/lib/rustlib/x86_64-unknown-freebsd/lib/libstd-441959e578fbfa8d.so (0x25f114c46000)
libc.so.7 => /lib/libc.so.7 (0x25f115e0f000)
libthr.so.3 => /lib/libthr.so.3 (0x25f114e6b000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x25f11756d000)
[vdso] (0x25f1130d4000)
ldd 명령으로 동적 라이브러리 의존성을 확인하면 libstd-441959e578fbfa8d.so
가 링크된 것을 볼 수 있습니다.
libstd-441959e578fbfa8d.so
라는 파일명에서 알 수 있듯이, 러스트 컴파일러 버전이 바뀔 때마다 libstd
의 내부 구조가 변경될 수 있습니다. 이는 전통적인 동적 링킹의 최대 장점인 하위 호환성을 원천적으로 봉쇄합니다. 만약 앱이 libstd
에 동적으로 링킹되어 있다면, 시스템의 러스트 버전이 업데이트될 때마다 기존 앱은 작동하지 않을 가능성이 매우 큽니다. 또한, 시스템에서 rust
패키지를 삭제하면 libstd
도 함께 삭제되어 모든 관련 앱이 무력화될 수 있습니다.
이러한 러스트의 근본적인 ABI 불안정성 때문에, 실제 배포 환경에서 안정적인 작동을 위해서는 libstd를 바이너리 내부에 직접 포함시키는 정적 링킹이 사실상 유일하고 강제적인 선택지가 됩니다. 그리고 이것이 바로 바이너리 크기가 ‘비대해지는’ 가장 큰 이유입니다.
2. 현실의 증거: 실제 애플리케이션 비교
이러한 ‘비대함’은 이론에 그치지 않고, 실제 애플리케이션 비교를 통해 명확히 드러납니다.
사례 1: rg
(Rust) vs grep
(C)
러스트로 만들어진 grep
대체 유틸리티 rg
(ripgrep)의 바이너리 크기를 grep
과 비교해봅시다.
debian:~$ ls -l /bin/grep /bin/rg
-rwxr-xr-x 1 root root 203072 Nov 10 2020 /bin/grep
-rwxr-xr-x 1 root root 4345184 Jan 19 2021 /bin/rg
rg는 grep보다 약 21배나 더 큰 바이너리 크기를 보여줍니다. 물론 rg
가 grep
보다 병렬 처리 등 더 많은 기능을 제공한다는 반론이 있을 수 있습니다. 그 주장은 타당합니다. 하지만 기능 차이를 감안하더라도 20배가 넘는 크기 차이는 쉽게 납득하기 어렵습니다. 그렇다면, 기능적으로 훨씬 더 적은 경우는 어떨까요?
사례 2: kime
(Rust) vs nimf
(C) 입력기 비교
$ ls -al kime_ubuntu-22.04_v3.1.1_amd64.deb nimf_2023.01.26-bookworm_amd64.deb
-rw-r--r-- 1 user user 3197276 Jun 27 07:38 kime_ubuntu-22.04_v3.1.1_amd64.deb
-rw-r--r-- 1 user user 275728 Jun 27 07:37 nimf_2023.01.26-bookworm_amd64.deb
러스트 기반의 kime
패키지는 약 3.2MB인 반면, C 기반의 nimf
패키지는 약 0.28MB에 불과해 11배 이상의 차이를 보입니다. 기능적으로는 kime
가 한국어 입력에 집중하는 반면, nimf
는 한국어, 중국어, 일본어를 포함한 수십 종의 다양한 언어를 지원하는 프레임워크입니다.
특정 언어에 집중된 kime보다 기능적으로 훨씬 광범위한 nimf
가 패키지 크기가 10분의 1도 되지 않는다는 사실은, 러스트 바이너리의 ‘비대함’이 단순한 기능 추가만으로는 설명될 수 없는 근본적인 문제임을 시사합니다.
3. 커뮤니티의 해명과 그 이면: min-sized-rust
의 불편한 진실 (2025년 판)
이러한 비판에 대해 러스트 커뮤니티는 min-sized-rust
와 같은 문서를 통해 바이너리 크기를 줄일 수 있다고 주장합니다. 그러나 이 문서에서 제시하는 방법들은 대부분 비현실적인 꼼수이거나 러스트의 장점을 포기해야 하는 것들입니다.
비교 기준점 설정: C 언어 Hello World
먼저, 비교를 위한 기준점을 명확히 해야 합니다. 다음은 기본적인 C ‘Hello World’ 코드입니다.
#include <stdio.h>
int main ()
{
puts ("hello world");
return 0;
};
이 코드를 cc -O2 -o hello hello.c 명령어로 컴파일하고, strip hello 명령어로 불필요한 정보를 제거하면, 최종 바이너리 크기는 4,960바이트 (약 5KB)입니다. 이는 특별한 ‘꼼수’가 아닌, C 언어에서 일반적으로 바이너리를 최적화하는 과정입니다. 이 5KB라는 크기를 기준으로 러스트의 결과물을 평가해 보겠습니다.
규범적인 방법과 그 한계
min-sized-rust 가이드는 먼저 Cargo.toml에 몇 가지 설정을 추가하는 ‘규범적인 방법’을 제시합니다.
[profile.release]
strip = true # 심볼 자동 제거
opt-level = "z" # 속도보다 크기에 최적화
lto = true # 링크 타임 최적화
codegen-units = 1 # 최적화 기회 극대화
이러한 표준적인 최적화를 모두 적용해도, ‘Hello World’ 바이너리의 크기는 277KB 수준에 머뭅니다. C의 5KB에 비하면 여전히 50배 이상 큰, 실망스러운 결과입니다.
동작을 바꾸는 최적화: panic = "abort"
가이드는 다음 단계로 panic = "abort"
옵션을 제시합니다. 이는 패닉 발생 시 스택을 풀지 않고 즉시 프로그램을 중단시켜 바이너리 크기를 줄이는 방법입니다. 하지만 가이드 스스로 “프로그램의 동작에 영향을 미친다”고 경고합니다. 이는 안정성을 위해 편리함을 포기하는 첫 번째 단계입니다.
‘꼼수’의 영역: 나이틀리(Nightly)와 불안정한 기능들
의미 있는 크기 감소를 위해서는 결국 안정 버전(stable)에서는 사용할 수 없는 나이틀리 전용 기능에 의존해야 합니다. 가이드에서 가장 극적인 크기 감소를 보여주는 build-std
기능이 대표적입니다. 이 방법은 rustup
으로 nightly
툴체인과 rust-src
컴포넌트를 설치하고, 다음과 같이 매우 복잡하고 긴 명령어를 사용해야 합니다.
# macOS 기준 예시
$ RUSTFLAGS="-Zlocation-detail=none -Zfmt-debug=none" cargo +nightly build \
-Z build-std=std,panic_abort \
-Z build-std-features="optimize_for_size,panic_immediate_abort" \
--target x86_64-apple-darwin --release
가이드는 이 모든 과정을 거치면 최종 바이너리가 30KB까지 줄어든다고 주장합니다. 30KB라는 수치는 인상적이지만, 이는 다음과 같은 사실을 감추고 있습니다.
- 비현실성: 안정 버전에서는 불가능하며, 수많은 불안정(
-Z
) 플래그와 복잡한 빌드 과정을 거쳐야만 얻을 수 있습니다. 이는 표준적인 개발 방식이 아닙니다. - 여전한 크기: 이렇게까지 ‘꼼수’를 부려야 얻는 30KB라는 크기조차, C의 5KB와 비교하면 여전히 6배나 큽니다.
러스트의 장점을 포기하는 극단적 선택
가이드는 여기서 더 나아가 no_main
과 no_std
라는 극단적인 방법을 제시합니다.
no_main
: 가이드 스스로 “코드가 해킹스럽고 이식 불가능할 것으로 예상하라(Expect the code to be hacky and unportable)”고 경고합니다.no_std
: “대부분의 Rust 크레이트에 대한 액세스 권한을 잃게 될 것”이라며, 러스트 생태계의 장점을 포기해야 함을 인정합니다.
이 단계에 이르면, 개발자는 러스트의 안전성과 생태계라는 장점을 거의 모두 포기하고 사실상 C와 다름없는 방식으로 코딩하게 됩니다. 이는 바이너리 크기를 줄이기 위해 러스트를 사용하는 이유 자체를 없애는 모순입니다.
2025년에도 변하지 않은 불편한 진실
이상의 검증을 통해, 2025년의 min-sized-rust
가이드 역시 본질은 변하지 않았음을 알 수 있습니다. 이 가이드는 러스트 바이너리를 작게 만들 수 있는 ‘가능성’을 보여주지만, 그 방법들은 대부분 실용성이 떨어지거나, 불안정하거나, 러스트의 핵심 가치를 포기해야 하는 것들입니다.
따라서 min-sized-rust
가이드를 근거로 ‘러스트 바이너리도 작아질 수 있다’고 주장하기 전에, 그 방법들이 갖는 현실적인 제약과 트레이드오프를 명확히 인지할 필요가 있습니다. 표준적인 개발 방식으로는 바이너리 크기가 여전히 큰 것이 현실이며, 이 점에 대한 보다 균형 잡힌 논의가 필요합니다.
‘메모리 안전성’이 곧 ‘애플리케이션 안정성’을 의미하는가?: librsvg
사례 분석
메모리 안전성(memory-safe)을 보장하는 러스트를 사용하면 애플리케이션이 전반적으로 안전해질 것이라는 기대는 합리적입니다. 그러나 ‘메모리 안전성’이 과연 ‘애플리케이션의 전반적인 안정성’과 동일한 의미인지, C에서 러스트로 성공적으로 포팅된 대표적인 사례인 librsvg
를 통해 비판적으로 검토해 보겠습니다.
librsvg
는 SVG(Scalable Vector Graphics) 파일을 렌더링하는 라이브러리로, 2016년 10월경부터 C에서 러스트로의 전환을 시작한 것 같습니다.
- 포팅 시작 커밋 (추정):
https://gitlab.gnome.org/GNOME/librsvg/-/commit/f27a8c908ac23f5b7560d2279acec44e41b91a25
2025년 6월 기준, librsvg
의 코드베이스는 대부분 러스트로 구성되어 있음을 확인할 수 있습니다.
Rust 87.0%
C 7.7%
Python 2.1%
Meson 1.8%
Shell 1.1%
Batchfile 0.3%
그렇다면, 대부분이 러스트로 재작성된 librsvg는 과연 완벽하게 ‘안전’해졌을까요? 프로젝트의 이슈 트래커를 살펴보면 현실을 마주하게 됩니다.
- panic 관련 이슈 검색: https://gitlab.gnome.org/GNOME/librsvg/-/issues/?search=panic&sort=created_date&state=opened&first_page_size=20
- faults 관련 이슈 검색: https://gitlab.gnome.org/GNOME/librsvg/-/issues/?search=faults&sort=created_date&state=opened&first_page_size=20
이슈 트래커의 현실은, ‘메모리 안전성’을 확보했음에도 불구하고 panic
이나 fault
로 인한 비정상적인 종료가 여전히 발생하고 있음을 보여줍니다. 이는 러스트가 특정 유형의 메모리 오류를 원천적으로 막아줄지는 몰라도, 애플리케이션의 모든 잠재적 충돌을 방지하지는 못한다는 것을 의미합니다. ‘메모리 안전성’이 ‘무결점’이나 ‘무중단’을 보장하는 만병통치약이 아니라는 것입니다.
게다가 이러한 언어 전환은 또 다른 현실적인 문제를 야기했습니다. 바로 바이너리 크기의 폭증입니다. C로 작성되었던 과거 버전과 러스트로 재작성된 현재 버전의 패키지 크기를 비교해 보겠습니다.
$ ls -l librsvg*~*
-rw-r--r-- 1 root wheel 201832 Jun 25 05:13 librsvg2-2.40.21_4~f605eba4b2.pkg
-rw-r--r-- 1 root wheel 3202515 Jun 24 10:28 librsvg2-rust-2.60.0~c96f7b1992.pkg
C 기반의 librsvg
2.40 버전 패키지는 약 0.2MB였지만, 러스트로 포팅된 2.60 버전 패키지는 약 3.2MB로, 크기가 16배 가까이 폭증했습니다. 물론 두 패키지 사이에는 상당한 버전 차이가 존재하며, 기능 추가가 크기 증가에 기여했음을 부정할 수는 없습니다. 하지만 앞선 kime
사례처럼 기능이 더 적은 경우에도 파일 크기가 10배 이상 차이 났던 것을 고려하면, 크기 증가분 전부를 기능 개선만으로 설명하기는 어렵습니다.
이처럼 librsvg 사례는 러스트로의 전환이 ‘메모리 안전성’을 얻는 대신 ‘애플리케이션 안정성’과 ‘배포 효율성’ 측면에서 새로운 트레이드오프를 만들어냈음을 명확히 보여줍니다. 실무 환경에서 기술을 채택할 때에는, 인터넷의 평가나 마케팅 구호에 의존하기보다, 여러 프로젝트의 이슈 트래커를 직접 확인하고 발생 가능한 문제들을 다각적으로 검토하는 비판적인 자세가 반드시 필요합니다.
러스트의 경쟁자는 C++ ?
러스트는 ‘메모리 안전성’을 전면에 내세우지만, 사실 자바(Java), C#, 파이선(Python), 자바스크립트(JavaScript), Go, 스위프트(Swift), 에이다(Ada), 코틀린(Kotlin), 루비(Ruby), PHP 등 TIOBE 지수 20위권 내의 많은 주류 언어들 역시 메모리 안전성을 기본적으로 지원합니다. 유독 러스트만 이를 특별한 장점처럼 부각하는 것은, 기술의 복잡한 트레이드오프보다 ‘안전’이라는 가치를 과도하게 강조하는 경향으로 비칠 수 있습니다.
이 모든 점을 고려할 때, 러스트가 C/C++을 모든 영역에서 대체하기는 어렵습니다. 앞서 지적한 불안정한 ABI, 상대적으로 큰 바이너리 크기, 패닉으로 인한 프로그램 중단 가능성, 복잡한 C ABI 연동, 가파른 학습 곡선, 코딩 생산성 등의 현실적인 트레이드오프가 존재하기 때문입니다.
물론 러스트의 적절한 활용처는 분명 존재합니다. 바로 성능과 보안을 동시에 극도로 요구하는 일부 시스템 프로그래밍 영역입니다. 예를 들어, 고객 정보를 다루는 서버나 클라우드 인프라처럼 C/C++에서 발생하는 잘못된 메모리 참조로 인한 정보 노출이 심각한 손실로 이어질 수 있는 곳입니다. 이런 환경에서는 자바보다 빠르면서 자바 수준의 안정성을 기대할 수 있는 러스트가, 개발 비용을 감수하고서라도 충분히 고려해 볼 만한 대안이 될 수 있습니다.
하지만 이를 일반화하여 “C/C++은 안전하지 않으니 모든 것을 러스트로 대체해야 한다”고 주장하는 것은, 현실을 무시한 단편적인 논리일 수 있습니다. 이는 마치 모든 도로의 자동차를 최고 안전 등급의 장갑차로 교체하자고 주장하는 것과 같습니다. 각 도구는 그에 맞는 트레이드오프와 사용처가 있는 법입니다.
메모리 누수도, 프로그램의 갑작스러운 중단(패닉)도 여전히 프로그래머의 몫으로 남아있는데, ‘메모리 안전성’ 하나만으로 모든 것이 해결되는 것처럼 이야기하는 것은 정확한 표현이라고 보기 어렵습니다.
러스트의 사용처, ‘틈새시장’의 성장을 ‘대세’로 착각하지 말라
최근 몇 년간 러스트는 개발자 설문조사에서 ‘가장 사랑받는 언어’로 꾸준히 선정되고, 주요 빅테크 기업들이 채택하며 괄목할 만한 성장을 이룬 것은 사실입니다. 하지만 이러한 성장의 이면을 냉정하게 들여다볼 필요가 있습니다.
러스트의 성장은 운영체제, 클라우드 인프라, 블록체인 등 그것이 가장 빛을 발하는 시스템 프로그래밍이라는 틈새시장에 대부분 집중되어 있습니다. 소프트웨어 산업의 일자리 대부분을 차지하는 광범위한 비즈니스 애플리케이션 개발 영역에서 러스트의 채택률은 그 명성에 미치지 못하는 것이 현실입니다.
따라서 ‘가장 사랑받는 언어’라는 타이틀이 ‘가장 많은 일자리가 있는 언어’라는 의미와는 다르다는 현실을 직시해야 합니다. 러스트에 대한 열광적인 분위기에 휩쓸려 ‘장밋빛 미래’를 기대하기 전에, 이것이 소수의 전문가를 위한 틈새시장의 성장인지, 아니면 나의 커리어에 실질적인 기회를 제공할 수 있는 주류 시장의 변화인지 구별해야 합니다.
러스트의 특정 사용처를 목표로 하는 소수의 전문가가 아니라면, 대다수 개발 지망생에게는 여전히 C/C++, 자료구조, 네트워크 등 컴퓨터 과학의 근본을 다지는 것이 훨씬 더 안정적이고 폭넓은 기회를 제공합니다. 특정 언어의 유행에 편승하기보다, 어떤 언어에도 적용할 수 있는 보편적이고 중요한 지식을 쌓는 것이 장기적으로 더 현명한 투자일 것입니다.
결론: 그럼에도 불구하고 러스트가 갖는 명확한 가치
이 모든 비판에도 불구하고, 러스트가 특정 영역에서 왜 그토록 강력한 지지를 받는지에 대한 이유 또한 명확히 인정해야 합니다.
C/C++에서 잘못된 메모리 참조는 때로는 감지되지 않은 채로 프로그램의 오작동을 유발하거나, 최악의 경우 민감 정보가 노출되는 심각한 보안 취약점으로 이어집니다.
러스트는 바로 이 지점에서 타협하지 않는 가치를 제공합니다. 소유권 시스템과 빌림 검사기는 컴파일 시점에 수많은 잠재적 메모리 오류를 원천적으로 차단하며, 설령 런타임에 예측하지 못한 메모리 접근 오류가 발생하더라도 정의되지 않은 동작(Undefined Behavior) 대신 즉각적인 패닉(panic)으로 프로그램을 중단시켜 더 큰 재앙을 막습니다.
결국 이 두 가지 강력한 메커니즘이 합쳐져, 러스트의 핵심 가치인 ‘신뢰성과 보안성 향상’이 완성됩니다. 이 글에서 지적한 수많은 트레이드오프는 바로 이 핵심 가치를 얻기 위한 대가인 셈입니다.