← Rust 강의 목록으로
🦀
소유권
소유권 · 선수: 기초 1~5강

06. 소유권의 세 규칙 + move/copy

소유권(ownership) 은 Rust 가 GC 없이 메모리 안전을 보장하는 핵심 메커니즘입니다. 세 가지 규칙만 익히면 됩니다 — 각 값은 정확히 한 소유자, 소유자가 스코프를 벗어나면 값은 drop, 대입·인자 전달 시 소유권이 이동(move). 이 강의에서 move 와 copy 의 차이, Drop trait 의 의미를 짚고 다음 강의의 borrow 로 자연스럽게 이어집니다.

Rust소유권ownershipmovecopyDrop
소요 시간
약 1.5시간
난이도
📊 중급
선수 조건
🎯 기초 1~5강
결과물
소유권(ownership) 은 Rust 가 GC 없이 메모리 안전을 보장하는 핵심 메커니즘입니다. 세 가지 규칙만 익히면 됩니다 — 각 값은 정확히 한 소유자, 소유자가 스코프를 벗어나면 값은 drop, 대입·인자 전달 시 소유권이 이동(move). 이 강의에서 move 와 copy 의 차이, Drop trait 의 의미를 짚고 다음 강의의 borrow 로 자연스럽게 이어집니다.

이 강의에서 배우는 것

  • 1소유권 세 규칙을 자신의 말로 설명한다
  • 2move 가 일어나는 시점을 코드에서 짚는다
  • 3Copy 트레잇이 구현된 타입과 그렇지 않은 타입을 구분한다
  • 4Drop 이 호출되는 시점을 이해한다
  • 5use-after-move 컴파일 에러를 보고 원인을 짚는다

소개

다른 언어는 메모리 관리를 두 방향 중 하나로 풉니다 — **수동 (C/C++ malloc/free)** 또는 **자동 GC (Java/Python)**. 수동은 빠르지만 위험하고, GC 는 안전하지만 STW·예측 불가 지연이 단점. Rust 는 제3의 길을 택했습니다 — **소유권 규칙을 컴파일러가 검증**하여 GC 없이 안전을 얻습니다.

핵심 개념

1) 세 가지 규칙

  1. 각 값은 **정확히 하나의 소유자(owner)** 를 가진다
  2. 소유자는 한 번에 하나만 존재 가능
  3. 소유자가 **스코프를 벗어나면** 값은 자동으로 drop 된다

2) move — 소유권 이동

힙 데이터를 가진 값(예: String, Vec) 을 변수에 대입하거나 함수에 넘기면 **소유권이 이동** 합니다. 원본은 더 이상 유효하지 않습니다.

rust
let s1 = String::from("hello");
let s2 = s1;          // s1 의 소유권이 s2 로 이동 (move)
println!("{}", s1);   // error[E0382]: borrow of moved value: `s1`

3) Copy — 비트 단위 복사가 안전한 타입

i32 / f64 / bool / char / 고정 크기 배열 처럼 **스택에만 사는 값** 은 Copy 트레잇이 구현돼 있어 대입 시 move 가 아니라 복사가 일어납니다. 그래서 원본도 그대로 살아 있습니다.

rust
let x = 5;
let y = x;            // copy
println!("{} {}", x, y); // OK — 둘 다 살아있음

4) Drop — 메모리 해제 후크

소유자가 스코프를 벗어나는 시점에 Drop::drop 이 자동으로 호출돼 힙 메모리·파일 핸들·소켓 등이 해제됩니다. 이걸 **RAII** (Resource Acquisition Is Initialization) 라 부릅니다.

타입대입 시 동작이유
i32, bool, charCopy스택 데이터, 비트 복사 안전
String, Vec<T>Move힙 포인터 — 두 소유자 → double-free 위험
&T (참조)Copy다음 강의 — 빌림은 소유권 없음
(i32, i32)Copy구성 요소가 모두 Copy
(i32, String)MoveString 이 Move 이므로 전체 Move

핵심 예제

함수 인자로 넘기면 move 가 일어납니다:

rust
fn takes_ownership(s: String) {
    println!("{}", s);
} // 여기서 s 가 drop

fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    // println!("{}", s); // error — s 는 이미 move 됨
}

소유권을 돌려받는 패턴 (다음 강의의 borrow 가 더 편하지만, 비교용):

rust
fn take_and_give(s: String) -> String {
    println!("len={}", s.len());
    s
}

fn main() {
    let s1 = String::from("hello");
    let s2 = take_and_give(s1);
    println!("{}", s2); // OK — s2 가 새 소유자
}

Drop 관찰 — 커스텀 타입에 Drop 구현:

rust
struct Resource { name: String }
impl Drop for Resource {
    fn drop(&mut self) { println!("drop: {}", self.name); }
}
fn main() {
    let _r = Resource { name: "file.txt".into() };
    println!("end of main");
} // 출력 순서: "end of main" → "drop: file.txt"

자주 하는 실수

Q. String 을 함수에 넘기고 나서 다시 쓰고 싶은데 매번 돌려받아야 하나요?

A. 그것이 다음 강의의 **borrow(&)** 가 푸는 문제입니다. 소유권 이동 없이 잠깐 빌려주기. 이 강의에서는 일단 move 의 작동 원리만 익히세요.

Q. let s2 = s1.clone() 이면 두 변수 다 살아있던데요?

A. `.clone()` 은 **명시적 깊은 복사**. 새 힙 메모리를 할당해 데이터를 복제하므로 양쪽 다 독립적인 String 이 됩니다. 그 대신 비용(메모리·시간) 이 발생.

Q. struct 에 Copy 를 어떻게 붙이나요?

A. `#[derive(Copy, Clone)]` 어트리뷰트. 단, 구성 요소가 **모두 Copy** 여야 가능. String 을 필드로 가진 struct 는 Copy 불가.

정리

  • 각 값은 한 소유자, 소유자 스코프 끝 = drop
  • 힙 데이터 대입·전달 = move, 원본 무효화
  • 스택만 사는 타입은 Copy 라 대입 시 복사
  • Drop 트레잇이 RAII 자원 해제를 자동화

과제

  1. String 을 만들고 함수에 넘긴 뒤 호출자에서 다시 쓰려고 시도해 컴파일 에러 메시지를 확인
  2. Resource 구조체를 두 개 만들고 main 끝에서 drop 순서가 어떻게 되는지 출력으로 확인 (LIFO 인지)
  3. i32 만 들어 있는 struct 와 String 이 들어 있는 struct 두 개를 만들고 둘 다 Copy 가 되는지 derive 로 시도
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗