hyunsu blog

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

Rust 함수와 소유권 — 값을 넘기면 함수가 주인이 된다

목차 · 3
  1. 01넘기는 것도 이동이다
  2. 02돌려주면 주인이 돌아온다
  3. 03실습 — 숫자를 넘기면

Rust 에서 String 을 함수에 넘기면 주인이 함수의 매개변수로 옮겨 간다. 그 뒤에 원래 변수를 쓰면 E0382 가 난다. 함수가 값을 돌려주면 주인도 다시 돌아온다. 정수 같은 Copy 타입은 복사되어 넘어가니 상관없다.

01 넘기는 것도 이동이다

이름을 받아 크게 외치는 함수를 만들고, 부른 뒤에 이름을 한 번 더 찍어 보자.

rust
fn shout(text: String) {
    println!("{text}!!!");
}

fn main() {
    let name = String::from("ferris");
    shout(name);
    println!("{name}");
}
text
error[E0382]: borrow of moved value: `name`
 --> src/main.rs:8:16
  |
6 |     let name = String::from("ferris");
  |         ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
7 |     shout(name);
  |           ---- value moved here
8 |     println!("{name}");
  |                ^^^^ value borrowed here after move
  |
note: consider changing this parameter type in function `shout` to borrow instead if owning the value isn't necessary
 --> src/main.rs:1:16
  |
1 | fn shout(text: String) {
  |    -----       ^^^^^^ this parameter takes ownership of the value
  |    |
  |    in this function
help: consider cloning the value if the performance cost is acceptable
  |
7 |     shout(name.clone());
  |               ++++++++
  1. 무엇 — 15편과 같은 E0382, 이동한 값 name 을 썼다.
  2. 어디 — 7번째 줄 shout(name) 에서 값이 옮겨 갔다(value moved here). 함수에 넘기는 것도 let b = a; 와 같은 이동이다. name 의 값은 매개변수 text 가 갖게 된다.
  3. 고치는 법 — 이번에는 제안이 둘이다. note: 는 함수 쪽을 짚는다. 1번째 줄의 text: String 이 "값의 주인이 되는 매개변수(takes ownership)" 이니, 주인이 될 필요가 없다면 빌리는(borrow) 타입으로 바꾸라고 한다. help: 는 15편처럼 .clone() 을 권한다.

shout 안에서 text 는 주인이다. 함수가 끝나면(블록을 벗어나면) 15편의 규칙 3 대로 문자열이 치워진다. 그래서 main 으로 돌아왔을 때 name 에는 아무것도 없다.

02 돌려주면 주인이 돌아온다

함수가 받은 값을 돌려주면, 주인이 다시 부른 쪽으로 옮겨 온다.

rust
fn shout(text: String) -> String {
    println!("{text}!!!");
    text
}

fn main() {
    let name = String::from("ferris");
    let name = shout(name);
    println!("still have: {name}");
}
text
   Compiling shout v0.1.0 (~/projects/shout)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/shout`
ferris!!!
still have: ferris

shout 의 마지막 줄 text 에 세미콜론이 없으니 반환값이다(11편). let name = shout(name); 은 넘긴 값을 돌려받아 같은 이름에 다시 담는다. 4편의 가림이다.

동작은 하지만 번거롭다. 값을 잠깐 보기만 하는 함수도 매번 돌려줘야 하고, 돌려줄 값이 따로 있으면 11편의 튜플로 묶어야 한다. 그래서 Rust 에는 주인을 넘기지 않고 빌려주는 방법이 있다. 오류의 note: 가 말한 "borrow" 다. 다음 편에서 다룬다.

03 실습 — 숫자를 넘기면

Copy 타입은 함수에 넘겨도 이동하지 않는다. 복사본이 넘어가기 때문이다.

rust
fn double(n: i32) -> i32 {
    n * 2
}

fn main() {
    let count = 21;
    let twice = double(count);
    println!("{count} -> {twice}");
}
text
   Compiling double v0.1.0 (~/projects/double)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/double`
21 -> 42

double(count) 뒤에도 count 를 그대로 쓸 수 있다. 10편부터 숫자를 받는 함수를 여러 번 썼지만 오류가 없었던 이유가 이것이다.

fn-move-error 의 shout(name) 을 shout(name.clone()) 으로 바꿔 돌려 보자. 복사본을 넘기니 원래 name 은 main 에 남는다. 그다음 18편에서 같은 일을 복사 없이 해 본다.