Ownership
소유권
Rust의 핵심 메모리 관리 개념. 각 값은 하나의 소유자만 가지며, 스코프를 벗어나면 자동 해제. GC 없이 메모리 안전성 보장.
소유권
Rust의 핵심 메모리 관리 개념. 각 값은 하나의 소유자만 가지며, 스코프를 벗어나면 자동 해제. GC 없이 메모리 안전성 보장.
Ownership(소유권)은 Rust 프로그래밍 언어의 가장 핵심적이고 독창적인 개념입니다. 메모리 관리를 컴파일 타임에 검증하여 런타임 오버헤드 없이 메모리 안전성을 보장합니다. C/C++의 수동 메모리 관리로 인한 버그(dangling pointer, double free, use-after-free)와 GC 언어의 성능 문제를 동시에 해결하는 혁신적인 접근 방식입니다.
Borrowing(빌림)과 References(참조)는 Ownership을 보완하는 개념입니다. 소유권을 이전하지 않고 값을 임시로 빌려 사용할 수 있습니다. 불변 참조(&T)는 여러 개가 동시에 존재할 수 있지만, 가변 참조(&mut T)는 한 번에 하나만 허용됩니다. 이 규칙은 data race를 컴파일 타임에 방지합니다.
Ownership 시스템의 가장 큰 장점은 Garbage Collector 없이 메모리 안전성을 보장한다는 것입니다. GC가 없어 예측 가능한 성능과 낮은 메모리 오버헤드를 제공하며, 시스템 프로그래밍, 임베디드, 게임 엔진 등 성능이 중요한 분야에서 각광받고 있습니다. Borrow checker가 컴파일 시점에 모든 메모리 접근을 검증하므로 런타임 크래시가 발생하지 않습니다.
// === Ownership 기본 예제 ===
fn main() {
// 1. 기본 Ownership: String은 힙에 할당됨
let s1 = String::from("hello");
let s2 = s1; // s1의 소유권이 s2로 이동(move)
// println!("{}", s1); // 컴파일 에러! s1은 더 이상 유효하지 않음
println!("{}", s2); // OK: "hello"
// 2. Clone: 깊은 복사로 소유권 유지
let s3 = String::from("world");
let s4 = s3.clone(); // 힙 데이터까지 복사
println!("s3: {}, s4: {}", s3, s4); // 둘 다 유효
// 3. Copy trait: 스택 데이터는 자동 복사
let x = 5;
let y = x; // i32는 Copy trait 구현, x도 여전히 유효
println!("x: {}, y: {}", x, y); // 둘 다 유효
// 4. 함수와 Ownership
let s5 = String::from("rust");
takes_ownership(s5); // s5의 소유권이 함수로 이동
// println!("{}", s5); // 컴파일 에러!
let s6 = gives_ownership(); // 함수로부터 소유권 받음
println!("받은 값: {}", s6);
}
fn takes_ownership(s: String) {
println!("소유권 받음: {}", s);
} // s가 drop됨
fn gives_ownership() -> String {
String::from("새로운 문자열") // 소유권 반환
}
// === Borrowing과 References ===
fn borrowing_example() {
let s1 = String::from("hello");
// 불변 참조 (Immutable borrow)
let len = calculate_length(&s1); // 빌려주기만 함
println!("'{}' 길이: {}", s1, len); // s1 여전히 유효
// 가변 참조 (Mutable borrow)
let mut s2 = String::from("hello");
change(&mut s2);
println!("변경됨: {}", s2); // "hello, world"
// 참조 규칙: 불변 참조 여러 개 OK
let r1 = &s1;
let r2 = &s1;
println!("{} {}", r1, r2);
// 가변 참조는 단 하나만!
let mut s3 = String::from("test");
let r3 = &mut s3;
// let r4 = &mut s3; // 컴파일 에러! 동시에 두 개의 가변 참조 불가
println!("{}", r3);
}
fn calculate_length(s: &String) -> usize {
s.len() // 참조로 받았으므로 소유권 이동 없음
}
fn change(s: &mut String) {
s.push_str(", world"); // 가변 참조로 수정 가능
}
"Ownership은 Rust가 GC 없이 메모리 안전성을 보장하는 핵심 메커니즘입니다. 각 값은 단 하나의 소유자만 가지고, 소유자가 스코프를 벗어나면 자동으로 drop됩니다. Borrowing을 통해 소유권 이전 없이 값을 참조할 수 있는데, 불변 참조는 여러 개가 가능하지만 가변 참조는 동시에 하나만 허용됩니다. 이 규칙으로 data race를 컴파일 타임에 방지합니다."
"GC는 개발 편의성은 높지만 Stop-the-world 이슈로 실시간 시스템에 부적합합니다. Rust의 Ownership은 컴파일 타임에 메모리를 추적하니까 런타임 오버헤드가 없어요. 물론 borrow checker와 싸우느라 초기 러닝 커브가 높지만, 메모리 버그 없이 C++ 수준 성능을 얻을 수 있으니 시스템 프로그래밍에는 최적이죠. 저희 팀은 성능 크리티컬한 마이크로서비스를 Rust로 재작성해서 메모리 사용량을 60% 줄였습니다."
"여기 String을 함수에 넘길 때 clone() 쓰셨는데, 읽기만 하면 &str 참조로 빌려주세요. 불필요한 힙 할당이 줄어들어요. 그리고 이 루프에서 Vec을 반환할 때 소유권이 이동하니까 다음 이터레이션에서 에러 날 거예요. iter() 대신 into_iter() 쓰거나, 참조로 처리하시는 게 맞습니다."
정수, 부동소수점, bool, char 등 스택에 저장되는 타입은 Copy trait을 구현하여 자동 복사됩니다. 그러나 String, Vec, Box 등 힙 데이터를 가진 타입은 Move됩니다. Copy 가능 여부를 혼동하면 예상치 못한 컴파일 에러가 발생합니다.
참조를 반환하거나 구조체에 저장할 때 Lifetime annotation('a)이 필요합니다. Lifetime은 참조가 유효한 범위를 명시하여 dangling reference를 방지합니다. 처음에는 복잡해 보이지만, 컴파일러 메시지를 따라가면 점차 이해할 수 있습니다.
borrow checker 에러가 발생하면 당황하지 말고 에러 메시지를 자세히 읽으세요. 대부분 소유권을 clone()으로 복사하거나, 참조 범위를 조정하거나, RefCell/Rc로 런타임 빌림 검사로 전환하면 해결됩니다. 무조건 clone()보다는 설계를 다시 검토하는 것이 좋습니다.