값 복제하기, 1부: Clone
이전 챕터에서 우리는 소유권(ownership)과 빌림(borrowing)에 대해 배웠습니다. 특히 다음 두 가지 중요한 원칙을 기억하실 겁니다.
- Rust의 모든 값은 특정 시점에 단 하나의 소유자만 가집니다.
- 함수가 값의 소유권을 가져가면(소비하면), 호출한 쪽에서는 더 이상 그 값을 사용할 수 없습니다.
하지만 실제 코드를 짜다 보면 이 규칙이 너무 까다롭게 느껴질 때가 있습니다. 가끔은 함수에 값을 넘겨주면서도, 그 함수가 끝난 뒤에 같은 값을 계속 써야 할 일이 생기기 때문이죠.
fn consumer(s: String) { /* 값을 소비하는 함수 */ }
fn example() {
let mut s = String::from("hello");
consumer(s);
s.push_str(", world!"); // 오류: 소유권이 이동(move)했으므로 여기서 사용할 수 없습니다.
}
바로 이런 상황에서 Clone 트레이트가 구원 투수로 등장합니다.
Clone 트레이트
Clone은 Rust 표준 라이브러리에 정의된 트레이트입니다.
pub trait Clone {
fn clone(&self) -> Self;
}
clone 메서드는 self에 대한 참조를 인자로 받아, 동일한 타입을 가진 완전히 새로운 소유권 있는 인스턴스를 만들어 반환합니다.
실제 사용 예시
위의 예제에서 consumer를 호출하기 전에 clone을 사용해 새로운 String 인스턴스를 만들면 문제를 해결할 수 있습니다.
fn consumer(s: String) { /* */ }
fn example() {
let mut s = String::from("hello");
let t = s.clone(); // s를 복제하여 새로운 인스턴스 t를 만듭니다.
consumer(t); // t의 소유권을 넘겨줍니다.
s.push_str(", world!"); // s는 여전히 살아있으므로 오류가 발생하지 않습니다!
}
s의 소유권을 직접 넘겨주는 대신, 똑같이 생긴 복사본(t)을 만들어 넘겨주는 방식입니다. 덕분에 s는 consumer 호출이 끝난 뒤에도 아무런 문제 없이 사용할 수 있습니다.
메모리에서는 어떤 일이 벌어질까요?
방금 본 예제에서 메모리가 어떻게 변하는지 살펴봅시다. 먼저 let mut s = String::from("hello");가 실행된 상태입니다.
s
+---------+--------+----------+
스택 | 포인터 | 길이 | 용량 |
| | | 5 | 5 |
+--|------+--------+----------+
|
|
v
+---+---+---+---+---+
힙(Heap):| H | e | l | l | o |
+---+---+---+---+---+
여기서 let t = s.clone()이 호출되면, 데이터를 똑같이 복사하기 위해 힙 영역에 완전히 새로운 메모리 공간을 할당합니다.
s t
+---------+--------+----------+ +---------+--------+----------+
스택 | 포인터 | 길이 | 용량 | | 포인터 | 길이 | 용량 |
| | | 5 | 5 | | | | 5 | 5 |
+--|------+--------+----------+ +--|------+--------+----------+
| |
| |
v v
+---+---+---+---+---+ +---+---+---+---+---+
힙(Heap):| H | e | l | l | o | | H | e | l | l | o |
+---+---+---+---+---+ +---+---+---+---+---+
Java와 같은 언어를 써보셨다면, clone을 객체의 **깊은 복사(deep copy)**를 수행하는 도구로 이해하시면 쉽습니다.
Clone 구현하기
여러분이 만든 타입이 복제 가능하게 하려면 Clone 트레이트를 구현해야 합니다. 다행히 직접 코드를 짤 필요 없이 대부분의 경우 derive 매크로를 사용해 간단히 해결할 수 있습니다.
#[derive(Clone)]
struct MyType {
// 필드들
}
이렇게 하면 컴파일러가 알아서 MyType의 모든 필드를 하나씩 복제한 뒤, 이를 조합해 새로운 인스턴스를 만드는 Clone 구현체를 생성해 줍니다. 이 자동 생성된 코드가 궁금하다면 cargo expand 명령어나 IDE의 기능을 활용해 확인해 볼 수 있습니다.
Exercise
The exercise for this section is located in 04_traits/11_clone