learn

Rust 소유권은 왜 만들어졌는가

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차 목적은 안전성

같이 보면 좋은 노트


한 줄 요약

소유권은 use-after-free, double free, dangling pointer, data race 같은 메모리 안전성 버그를 컴파일 타임에 차단하기 위한 메커니즘이다. Page fault나 메모리 효율과는 무관하며, GC 없이도 자동 메모리 관리를 가능케 한다는 부수 효과가 따라온다.