hyunsu blog

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

Rust clone 과 Copy — 복사해서 주인을 둘로 만든다

목차 · 3
  1. 01clone 으로 복사한다
  2. 02숫자는 저절로 복사된다
  3. 03실습 — String 이 든 튜플은 이동한다

Rust 에서 String 하나를 두 변수가 함께 쓰려면 .clone() 으로 값을 복사한다. 정수·실수·bool·char 처럼 작은 값은 Copy 타입이라, let y = x; 를 해도 이동하지 않고 저절로 복사된다. String 이 하나라도 든 튜플은 Copy 가 아니어서 이동한다.

01 clone 으로 복사한다

15편에서 컴파일러가 권한 대로 .clone() 을 붙여 보자.

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

a.clone() 은 a 의 문자열을 통째로 복사한 새 값을 만든다. b 는 그 새 값의 주인이다. 값이 둘이고 주인도 둘이니 규칙(주인은 하나)을 어기지 않는다. 둘 중 하나를 고쳐도 다른 쪽은 그대로다.

복사에는 대가가 있다. 문자열이 길면 그만큼 메모리를 새로 잡고 글자를 옮겨 적는다. 15편 오류의 help 가 "성능 비용을 감수할 수 있다면(if the performance cost is acceptable)" 이라고 단서를 단 이유다. 복사 없이 함께 쓰는 법은 18편의 참조다.

02 숫자는 저절로 복사된다

그런데 3편에서 let 으로 숫자를 이리저리 넣을 때는 아무 문제가 없었다.

rust
fn main() {
    let x = 5;
    let y = x;
    println!("x = {x}, y = {y}");

    let point = (3, 4);
    let other = point;
    println!("{point:?} {other:?}");
}
text
   Compiling copy v0.1.0 (~/projects/copy)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/copy`
x = 5, y = 5
(3, 4) (3, 4)

let y = x; 뒤에도 x 를 쓸 수 있다. 정수는 Copy 타입이라서 이동하는 대신 저절로 복사되기 때문이다. 정수 하나는 크기가 작고 정해져 있어서 복사하는 비용이 거의 없다. 그래서 Rust 는 굳이 이동시키지 않는다.

Copy 다 (저절로 복사)Copy 가 아니다 (이동)
정수 · 실수 · bool · charString
Copy 타입만 든 튜플·배열 — (3, 4)String 이 든 튜플 — (1, String)

두 번째 줄의 (3, 4) 도 정수 둘짜리 튜플이라 Copy 다. {point:?} 는 9편에서 본 대로 통째로 찍는 표시다.

03 실습 — String 이 든 튜플은 이동한다

튜플에 String 을 하나 넣으면 어떻게 될까. 돌리기 전에 짐작해 보자.

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

15편과 같은 E0382 다. 2번째 줄의 밑줄이 이유를 말한다. item 의 타입이 (i32, String) 이고, 이 타입은 Copy 를 갖추지 않았다(does not implement the Copy trait). 정수는 복사할 수 있어도 String 이 끼어 있으니 튜플 전체가 이동한다.

고치는 법은 두 가지다. help: 대로 item.clone() 으로 통째로 복사하거나, 두 변수를 다 쓸 필요가 없다면 other 만 쓴다. 다음 편에서는 함수에 값을 넘길 때도 이동이 일어난다는 것을 본다.