🗝️

소유권 — Ruby의 GC가 해주던 일을 직접 하기

Rubyist가 가장 어려워하는 Rust의 핵심 개념

Ruby에서 메모리 관리를 신경 쓴 적이 있는가? 아마 없을 것이다. GC가 다 해주니까. 객체를 만들고, 변수에 넣고, 메서드에 넘기고, 더 이상 안 쓰면 GC가 알아서 지운다.

Rust에는 GC가 없다. 대신 소유권이라는 규칙 세 가지로 메모리를 관리한다.

규칙 1: 모든 값에는 소유자가 하나

let s1 = String::from("hello");
let s2 = s1;  // s1의 소유권이 s2로 이동(move)
// println!("{}", s1);  // 컴파일 에러! s1은 더 이상 유효하지 않음

Ruby에서 s2 = s1을 하면 둘 다 같은 객체를 가리킨다. 둘 다 쓸 수 있다. Rust에서는 s1이 사라진다. s2만 남는다.

이게 Rubyist에게 가장 충격적인 부분이다. "변수에 넣었을 뿐인데 원본이 사라진다고?"

왜 이렇게 했나

Ruby의 GC는 런타임에 "이 객체를 아직 누가 쓰고 있나?"를 추적한다. 이게 CPU 시간과 메모리를 먹는다. Rust는 이 추적을 컴파일 타임에 하고, 런타임 비용을 0으로 만든다.

규칙 2: 빌림(borrow) — 참조로 넘기기

소유권을 넘기지 않고 빌려줄 수 있다.

fn print_len(s: &String) {  // &로 빌림
    println!("{}", s.len());
}

let s = String::from("hello");
print_len(&s);  // &로 빌려줌
println!("{}", s);  // s 여전히 사용 가능!

&는 "읽기만 할게, 소유권은 네가 가지고 있어"라는 뜻. Ruby에서는 이 구분이 필요 없었다. 모든 객체 전달이 참조 전달이니까.

규칙 3: 가변 빌림은 하나만

let mut s = String::from("hello");
let r1 = &s;      // 불변 참조 — OK
let r2 = &s;      // 불변 참조 하나 더 — OK
// let r3 = &mut s;  // 가변 참조 — 에러! 불변 참조가 있는 동안 불가

"여러 명이 읽는 건 OK, 한 명이 쓰는 것도 OK, 근데 읽는 사람이 있는데 누가 고치면 안 된다." 이 규칙이 data race를 컴파일 타임에 방지한다.

Clone — Ruby의 .dup

소유권을 넘기고 싶지 않고 빌림도 적절하지 않으면 복사한다.

let s1 = String::from("hello");
let s2 = s1.clone();  // 깊은 복사
println!("s1: {}, s2: {}", s1, s2);  // 둘 다 OK

Ruby의 .dup이나 .clone과 같다. 근데 성능 비용이 있으니 필요할 때만.

Copy — 스택 값은 예외

정수, 부울, 부동소수점 같은 작은 값은 자동 복사된다.

let x = 42;
let y = x;  // 복사됨 (move 아님)
println!("{}", x);  // OK! 정수는 Copy trait 구현

스택에 있는 작은 값은 복사 비용이 거의 없으니 자동으로 복사한다. String은 힙 데이터라 자동 복사 안 됨.

실전 패턴

대부분의 경우 이 순서로 생각하면 된다:

  1. 참조(&)로 빌려줄 수 있나? → 대부분 이걸로 충분
  2. 소유권을 넘겨야 하나? → 함수가 값을 소유해야 할 때만
  3. clone이 필요한가? → 위 둘 다 안 되면 복사

핵심 포인트

1

모든 값에 소유자 하나 — let s2 = s1;으로 소유권이 이동(move)

2

&로 빌려주면 소유권 유지 — 대부분 이걸로 충분

3

불변 참조 여러 개 OK, 가변 참조는 하나만 — data race 방지

4

clone()으로 깊은 복사 가능 — Ruby의 .dup

장점

  • GC 없이 메모리 안전 — 런타임 비용 제로
  • data race가 컴파일 타임에 잡힌다

단점

  • Ruby에 없는 개념이라 초반 학습 곡선이 가파르다
  • 컴파일러와 싸우는 시간이 처음엔 꽤 걸린다

사용 사례

함수에 데이터를 넘길 때 — 빌림 vs 이동 판단 멀티스레드 코드에서 데이터 공유