소유권과 빌림 — Rust가 GC 없이 메모리를 관리하는 방법
컴파일 타임에 메모리 안전성을 보장하는 Rust의 핵심 규칙
C/C++는 프로그래머가 메모리를 직접 관리한다. 실수하면 use-after-free, double free, 댕글링 포인터 — 보안 취약점의 70%가 여기서 나온다(Microsoft 통계). Java/Go/Python은 가비지 컬렉터(GC)로 해결하지만, GC 일시 정지와 메모리 오버헤드가 따라온다.
Rust는 제3의 방법을 택했다. 컴파일러가 소유권 규칙을 검사해서 메모리 버그를 코드 작성 시점에 잡는다. 런타임 비용이 0이다.
세 가지 규칙
1. 값에는 소유자가 하나만 존재한다. let s2 = s1;을 쓰면 s1의 소유권이 s2로 이동(move)한다. 이후 s1을 쓰면 컴파일 에러.
2. 빌림(borrow)은 두 종류. &x는 불변 참조(여러 개 동시 가능), &mut x는 가변 참조(하나만). 둘을 동시에 쓸 수 없다.
3. 참조는 소유자보다 오래 살 수 없다. 이게 라이프타임 규칙이고, 댕글링 포인터를 원천 차단한다.
처음 쓰면 뭐가 힘든가
컴파일러가 에러를 내뿜는 빈도에 멘탈이 흔들린다. 특히 구조체에 참조를 넣으려 할 때 라이프타임 명시 요구가 처음엔 벽처럼 느껴진다. 근데 이건 다른 언어에서 "런타임에 터지던 버그"를 컴파일 타임으로 끌어온 것이다. 에러 위치가 바뀐 거지 버그 수가 늘어난 게 아니다.
핵심 포인트
모든 값에는 소유자(owner)가 정확히 하나 존재한다
소유자가 스코프를 벗어나면 값이 자동으로 drop된다 (RAII)
불변 참조(&T)는 여러 개 동시 가능, 가변 참조(&mut T)는 하나만
참조가 유효한 동안 소유자는 값을 이동하거나 가변 빌림할 수 없다
컴파일러(borrow checker)가 이 규칙을 모두 정적으로 검증한다
장점
- ✓ 메모리 버그가 컴파일 타임에 잡힌다 — 런타임 크래시 대신 컴파일 에러
- ✓ GC 없음 — 런타임 오버헤드 제로, 예측 가능한 성능
- ✓ data race 방지 — 컴파일러가 동시 가변 접근을 차단
단점
- ✗ 학습 곡선이 가파르다 — borrow checker와의 싸움이 초반 허들
- ✗ 자기 참조 구조체(self-referential struct)가 기본적으로 불가능
- ✗ 프로토타이핑 속도가 GC 언어 대비 느리다