hyunsu blog

홈강좌Rust 입문·중급 47편 중 15편째

Rust 소유권과 이동 — 값의 주인은 하나다 (E0382)

목차 · 4
  1. 01규칙은 셋이다
  2. 02다른 변수에 넣으면 주인이 바뀐다
  3. 03주인이 블록을 나가면 값은 치워진다
  4. 04실습 — 이동한 변수에 새 값 넣기

Rust 의 값에는 주인(변수)이 하나뿐이다. let b = a; 처럼 String 을 다른 변수에 넣으면 주인이 b 로 옮겨 가고(이동, move), 그 뒤에 a 를 쓰면 E0382 로 컴파일이 멈춘다. 주인이 블록을 벗어나면 값은 저절로 치워진다.

01 규칙은 셋이다

Rust 에는 가비지 컬렉터가 없다. 대신 아래 세 규칙을 컴파일러가 따져서, 메모리를 언제 치울지 정한다.

  1. 값마다 주인(owner)인 변수가 있다.
  2. 주인은 한 번에 하나뿐이다.
  3. 주인이 블록을 벗어나면 값은 치워진다.

이 편부터는 숫자 대신 String 으로 예를 든다. String::from("hello") 는 늘이거나 고칠 수 있는 문자열을 만든다(자세한 것은 20편). 숫자와 달리 String 은 이 규칙이 눈에 보이게 드러난다. 숫자가 왜 다른지는 다음 편에서 본다.

02 다른 변수에 넣으면 주인이 바뀐다

rust
fn main() {
    let a = String::from("hello");
    let b = a;
    println!("{a} {b}");
}
text
   Compiling owner v0.1.0 (~/projects/owner)
error[E0382]: borrow of moved value: `a`
 --> src/main.rs:4:16
  |
2 |     let a = String::from("hello");
  |         - move occurs because `a` has type `String`, which does not implement the `Copy` trait
3 |     let b = a;
  |             - value moved here
4 |     println!("{a} {b}");
  |                ^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let b = a.clone();
  |              ++++++++

For more information about this error, try `rustc --explain E0382`.
error: could not compile `owner` (bin "owner") due to 1 previous error
  1. 무엇 — 이동한 값(moved value) a 를 썼다.
  2. 어디 — 밑줄 셋이 차례로 이야기를 들려준다. 2번째 줄에서 a 가 String 이라 이동하는 타입이고, 3번째 줄에서 값이 b 로 옮겨 갔고(value moved here), 4번째 줄에서 옮겨 간 뒤에 a 를 썼다.
  3. 고치는 법 — help: 가 .clone() 으로 복사하라고 한다. 복사는 다음 편에서 다룬다.

let b = a; 뒤로 문자열 "hello" 의 주인은 b 하나다. a 는 더는 아무것도 갖고 있지 않다. 그래서 b 만 쓰면 문제없다.

rust
fn main() {
    let a = String::from("hello");
    let b = a;
    println!("{b}");
}
text
   Compiling owner v0.1.0 (~/projects/owner)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running `target/debug/owner`
hello

주인이 둘이면 둘 다 블록을 벗어날 때 같은 값을 두 번 치우려 한다. 이동은 그런 일을 처음부터 막는다.

03 주인이 블록을 나가면 값은 치워진다

규칙 3 을 보자. 블록 안에서 만든 note 를 블록 밖에서 써 본다.

rust
fn main() {
    {
        let note = String::from("inside");
        println!("{note}");
    }
    println!("{note}");
}
text
error[E0425]: cannot find value `note` in this scope
 --> src/main.rs:6:16
  |
6 |     println!("{note}");
  |                ^^^^
  |
help: the binding `note` is available in a different scope in the same function
 --> src/main.rs:3:13
  |
3 |         let note = String::from("inside");
  |             ^^^^

무엇 줄이 "이 범위(scope)에서 note 를 찾을 수 없다" 고 말하고, help: 가 "note 는 같은 함수의 다른 범위에 있다" 며 3번째 줄을 짚는다. note 는 블록이 끝나는 5번째 줄의 } 에서 사라졌고, 그 순간 문자열도 치워졌다. 치우는 코드를 따로 쓰지 않아도 된다.

04 실습 — 이동한 변수에 새 값 넣기

이동한 변수는 비어 있을 뿐 없어진 것이 아니다. mut 라면 새 값을 넣어 다시 쓸 수 있다.

rust
fn main() {
    let mut a = String::from("first");
    let b = a;

    a = String::from("second");
    println!("a = {a}, b = {b}");
}
text
   Compiling owner v0.1.0 (~/projects/owner)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/owner`
a = second, b = first

"first" 는 b 로 옮겨 갔고, 빈 a 에는 새로 만든 "second" 가 들어갔다. 주인은 여전히 값마다 하나다. a = String::from("second"); 줄을 지우고 돌려 보면 처음과 같은 E0382 가 난다.

"그럼 a 와 b 를 둘 다 쓰고 싶으면?" 이 다음 질문이다. 다음 편에서 값을 복사하는 clone 과, 숫자가 이동하지 않는 이유인 Copy 를 다룬다.