Phase 1에서 Rust의 문법을 맛봤어요. 변수, 함수, 반복문, 벡터까지 — 다른 언어와 비슷한 부분이 많았죠?
이제부터는 Rust만의 독특한 규칙을 배울 거예요. 바로 소유권(ownership) 이에요. 소유권을 이해하면 모듈 06에서 clone()을 왜 써야 했는지, 그 에러가 왜 발생했는지 비로소 알게 돼요.
그런데 규칙을 배우기 전에, 먼저 왜 이런 규칙이 필요한지부터 알아볼게요.
메모리, 왜 신경 써야 할까?
프로그램이 실행되면 데이터를 메모리에 저장해요. 문제는 이 메모리를 잘못 관리하면 심각한 버그가 생긴다는 거예요.
아래는 C 언어 스타일의 의사 코드(pseudocode)예요. C를 몰라도 괜찮아요 — 어떤 문제가 생기는지만 보면 돼요.
// 의사 코드 — 메모리를 직접 관리하는 언어
data = 메모리_할당("안녕하세요")
pointer = data의_주소
메모리_해제(data) // 메모리를 반납했어요
print(pointer) // 반납한 메모리를 읽으려고 해요!
data의 메모리를 반납한 뒤에도 pointer는 여전히 그 주소를 기억하고 있어요. 이미 반납한 공간을 읽으면 쓰레기 값이 나오거나, 프로그램이 갑자기 꺼져요. 이걸 댕글링 포인터(dangling pointer) 라고 해요.
더 무서운 건, 이런 버그가 항상 바로 터지지 않는다는 거예요. 어떤 때는 잘 돌아가고, 어떤 때는 갑자기 죽어요. 찾기 정말 어려운 버그예요.
스택과 힙 — 메모리의 두 공간
컴퓨터 메모리에는 크게 두 공간이 있어요. 책상 위와 창고로 비유할게요.
| | 스택 (Stack) | 힙 (Heap) |
| ---- | --------------------------- | ---------------------------------- |
| 비유 | 책상 위 | 창고 |
| 특징 | 크기가 정해진 것만 올려놓음 | 크기가 큰 것, 변할 수 있는 것 보관 |
| 속도 | 빠름 | 상대적으로 느림 |
| 예시 | i32, bool, char | String, Vec |
| 정리 | 자동 (함수 끝나면 사라짐) | 누군가 정리해야 함 |
정수나 bool 같은 값은 크기가 정해져 있어서 책상 위(스택)에 올려놓으면 돼요. 함수가 끝나면 자동으로 치워져요.
문제는 String이나 Vec처럼 크기가 변할 수 있는 값이에요. 이런 값은 창고(힙)에 보관하는데, 사용이 끝나면 누군가 정리해야 해요. 안 치우면 메모리 누수(memory leak)가 생기고, 너무 일찍 치우면 댕글링 포인터가 생겨요.
다른 언어는 어떻게 하나요?
방법 1: 프로그래머가 직접 관리 (C, C++)
data = 메모리_할당("안녕하세요")
// ... 사용 ...
메모리_해제(data) // 직접 반납해야 해요
자유롭지만 위험해요. 깜빡하고 반납 안 하면 메모리 누수, 두 번 반납하면 프로그램 충돌이 일어나요.
방법 2: 가비지 컬렉터(GC) 사용 (Java, Python, JavaScript)
data = "안녕하세요"
// ... 사용 ...
// 반납은 GC가 알아서 해줘요!
편하지만, GC가 돌아갈 때 프로그램이 잠깐 멈추는 순간이 있어요. 게임이나 시스템 프로그래밍처럼 속도가 중요한 곳에서는 문제가 될 수 있어요.
방법 3: 소유권 시스템 (Rust)
fn main() {
let data = String::from("안녕하세요");
// data의 주인은 이 변수!
// 주인이 사라지면(스코프를 벗어나면) 메모리도 자동 정리
}
// <- 여기서 data의 메모리가 자동으로 정리돼요
Rust는 컴파일할 때 메모리 문제를 잡아내요. GC 없이도 안전하고, 프로그래머가 직접 반납할 필요도 없어요. 대신 "소유권"이라는 규칙을 따라야 해요.
"소유권" — 물건의 주인
소유권을 한마디로 말하면 이거예요.
모든 값에는 주인이 있고, 주인이 사라지면 값도 사라진다.
물건에 비유하면 쉬워요.
- 여러분이 가진 물건(값)에는 주인(변수) 이 있어요
- 주인이 이사를 가면(스코프를 벗어나면) 물건도 함께 치워져요
- 물건의 주인은 동시에 한 명이에요
이 간단한 규칙 덕분에 Rust는 "언제 메모리를 정리할지" 컴파일할 때 정확히 알 수 있어요. GC도 필요 없고, 프로그래머가 실수할 여지도 없어요.
더 알아보기: 실제로 발생한 메모리 버그들
메모리 관련 버그는 실제로 큰 보안 사고를 일으켜요.
- Heartbleed (2014): OpenSSL 라이브러리의 메모리 읽기 버그로 수백만 서버의 비밀 키가 노출됐어요
- Microsoft 보고: Windows 보안 취약점의 약 70%가 메모리 안전성 문제에서 비롯됐다고 해요
Rust의 소유권 시스템은 이런 종류의 버그를 컴파일 시점에 원천 차단해요. 프로그램을 실행하기도 전에 잡아내는 거예요.
앞으로 배울 내용
다음 모듈부터 소유권의 구체적인 규칙을 하나씩 배울 거예요.
| 모듈 | 내용 |
| ----- | ---------------------------------------- |
| 09 | 소유권의 3대 규칙, 값의 이동(move) |
| 10 | 빌림과 참조 — clone() 없이 값 사용하기 |
| 11 | String과 &str — 문자열과 소유권 |
| 12-13 | 구조체와 열거형 — 나만의 타입 만들기 |
| 14 | 소유권 실전 연습 |
모듈 06에서 clone()을 쓰며 "왜 이게 필요한 거지?"라고 생각했다면, 곧 그 이유를 알게 될 거예요!
Q1. 댕글링 포인터(dangling pointer)란 무엇인가요?
- A) 아직 할당되지 않은 메모리를 가리키는 포인터
- B) 이미 해제된 메모리를 가리키는 포인터
- C) 항상 null을 가리키는 포인터
- D) 두 개의 변수가 같은 메모리를 가리키는 포인터
Q2. Rust에서 String 값은 어디에 저장되나요?
- A) 스택에만 저장된다
- B) 힙에만 저장된다
- C) 스택에 메타데이터, 힙에 실제 데이터가 저장된다
- D) 컴파일러가 매번 다르게 결정한다
Q3. Rust의 소유권 시스템이 해결하려는 핵심 문제는?
- A) 코드를 더 짧게 작성하는 것
- B) 프로그램 실행 속도를 높이는 것
- C) GC 없이도 메모리를 안전하게 관리하는 것
- D) 멀티스레드 프로그래밍을 쉽게 만드는 것