Rust 소유권은 왜 만들어졌는가
소유권을 처음 배울 때 흔히 빠지는 오해가 있다. "소유권이 page fault를 줄이기 위해 있는 것 아닐까?", "메모리를 효율적으로 쓰기 위한 최적화 아닐까?" — 둘 다 틀렸다. 소유권은 메모리 안전성(memory safety) 보장을 위한 메커니즘이지, 성능 최적화가 아니다.
소유권이 푸는 진짜 문제
C/C++에서 수십 년간 버그의 원흉이었던 네 가지 문제를 컴파일 타임에 차단하는 것이 목적이다.
1. Use-after-free
해제된 메모리를 다시 사용하는 것.
char* p = new char[100];
delete[] p;
p[0] = 'x'; // ← 해제된 메모리 사용. 미정의 동작.
운 좋으면 segfault로 죽고, 운 나쁘면 보안 취약점이 된다.
2. Double free
같은 메모리를 두 번 해제하는 것.
delete[] p;
delete[] p; // ← heap corruption
3. Dangling pointer
이미 사라진 데이터를 가리키는 포인터.
int* foo() {
int x = 42;
return &x; // ← x는 함수가 끝나면 사라짐
}
4. Data race
여러 스레드가 동시에 같은 데이터를 수정.
// Thread 1 // Thread 2
shared_data.push_back(x); shared_data.push_back(y);
// 동시에 같은 메모리를 수정 → 정의되지 않은 결과
소유권 시스템은 이 모든 문제를 컴파일 타임에 차단하기 위해 설계되었다. *"각 값에는 소유자가 정확히 하나 있고, 소유자가 스코프를 벗어나면 값이 해제된다"*는 규칙이 use-after-free와 double free를 원천 봉쇄하고, 빌림 규칙이 dangling pointer와 data race를 막는다.
소유권은 page fault와 관련이 없다
Page fault는 운영체제 차원의 사건이다. 프로그램이 가상 주소에 접근했을 때 그 페이지가 물리 RAM에 없으면 OS가 디스크에서 가져와 매핑한다. 이건 어떤 언어로 짜든 똑같이 발생한다.
┌──────────────────────────────────────────────┐
│ 언어 레벨 (컴파일러) │
│ - 소유권, 빌림, 라이프타임 │ ← 소유권은 여기
│ - 타입 검사, scope 관리 │
├──────────────────────────────────────────────┤
│ 런타임 레벨 (프로세스) │
│ - 힙/스택 메모리 할당 │
│ - malloc/free, new/delete │
├──────────────────────────────────────────────┤
│ OS 레벨 (커널) │
│ - 가상 메모리, 페이지 테이블 │ ← Page fault는 여기
│ - swap, mmap, page fault 처리 │
├──────────────────────────────────────────────┤
│ 하드웨어 레벨 (MMU) │
│ - 물리 RAM, 캐시, TLB │
└──────────────────────────────────────────────┘
소유권은 맨 위층, page fault는 OS 층의 사건이다. 서로 다른 차원에 있어 직접 연결되지 않는다.
부수 효과로 따라오는 성능 이점
소유권의 1차 목적은 안전성이지만, 부수 효과로 몇 가지 성능 이점이 따라온다.
1. GC 없이 메모리 해제
Java나 Go는 garbage collector가 *"이 메모리를 아직 누가 쓰는가?"*를 런타임에 추적해서 회수한다. 이건 비용이 든다(GC pause, 메모리 오버헤드).
Rust는 소유권 규칙으로 *"소유자가 스코프를 벗어나는 그 시점에 해제"*가 컴파일 타임에 결정된다. GC 없이 자동 메모리 관리가 가능한 이유다. 이게 Rust가 시스템 언어로 분류되는 핵심이다.
2. 데이터 경합 방지로 동시성 안전
빌림 규칙(& vs &mut)이 *"같은 데이터에 대한 mutable 참조는 한 번에 하나"*를 강제한다. 컴파일 타임에 데이터 경합이 차단되어, 락 없이도 안전한 동시성 코드를 짤 수 있는 경우가 늘어난다.
3. 캐시 친화성 (간접 효과)
소유권이 명확하니까 데이터를 stack에 두는 패턴이 늘어난다. heap 할당을 줄이고 stack/inline 할당을 선호하게 되며, 이게 캐시 적중률을 높이긴 한다.
같은 문제를 푸는 다른 접근
| 언어 | 메모리 안전성 | 해결 방식 | 비용 |
|---|---|---|---|
| C/C++ | ❌ 프로그래머가 책임 | 수동 관리 (new/delete) | 인적 오류 위험 |
| Java/Go/Python | ✅ GC가 책임 | Garbage Collector | 런타임 비용, GC pause |
| Rust | ✅ 컴파일러가 책임 | 소유권 + 빌림 검사 | 학습 곡선, 일부 패턴 제약 |
Rust는 "안전성 + 런타임 비용 없음"이라는 두 마리 토끼를 동시에 잡으려고 소유권을 도입했다. 그게 Rust의 가장 큰 차별점이다.
자주 따라오는 오해들
- "소유권은 page fault를 줄이기 위함" — ❌ 무관함
- "소유권은 메모리 사용량을 줄이기 위함" — ❌ 사용량과 무관, 사용 안전성이 목적
- "소유권은 GC를 흉내내는 것" — △ 결과적으로 비슷한 효과를 내지만, 메커니즘은 완전히 다름. GC는 런타임 추적, 소유권은 컴파일 타임 분석
- "소유권은 성능 최적화 도구" — △ 성능 이점이 있지만 그건 부수 효과. 1차 목적은 안전성
같이 보면 좋은 노트
- stack-vs-heap-in-ownership — 4가지 문제가 stack/heap 어디에서 일어나는지
- copy-vs-move — 소유권 이동의 실제 메커니즘
- borrowing-rules — 빌림 규칙과 컴파일 타임 안전성
한 줄 요약
소유권은 use-after-free, double free, dangling pointer, data race 같은 메모리 안전성 버그를 컴파일 타임에 차단하기 위한 메커니즘이다. Page fault나 메모리 효율과는 무관하며, GC 없이도 자동 메모리 관리를 가능케 한다는 부수 효과가 따라온다.