6. Rust copy와 clone 정리(깊은 복사, 얕은 복사 포함)
clone과 copy
Rust에서 Copy
와 Clone
은 두 가지 서로 다른 트레이트(trait)입니다. 이 두 트레이트는 값의 복사와 복제를 다르게 처리합니다.
Copy 트레이트
Copy
트레이트는 값의 복사를 나타내며, 스택에 저장된 값의 복사본이 생성됩니다.Copy
트레이트는 스택에 저장된 고정 크기의 값들에 대해서만 적용됩니다.- 변수를 다른 변수에 할당할 때나 함수로 전달할 때 복사가 발생합니다.
Copy
트레이트를 구현하는 타입은 일반적으로Copy
성격을 가진 기본 타입(primitive type)들이며,Clone
트레이트를 구현하지 않습니다.
Clone 트레이트
Clone
트레이트는 값의 복제를 나타내며, 힙에 저장된 값의 복사본이 생성됩니다.Clone
트레이트는 값의 복제가 필요한 경우에 사용됩니다.Clone
트레이트를 구현하는 타입은 *Copy
와는 달리 복제를 지원하는 사용자 정의 타입에 적용됩니다.
예를 들어, 다음과 같은 코드에서는 Copy
트레이트가 사용됩니다.
1
2
3
let x = 5;
let y = x; // 복사 발생
반면에, Clone
트레이트는 사용자 정의 타입에서 직접 구현해야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
#[derive(Debug, Copy, Clone)]
struct MyStruct {
x: i32,
y: i32,
}
fn main() {
let original = MyStruct { x: 10, y: 20 };
let copied = original; // !!! 복사 발생 !!!
println!("{:?}", original); // 복사 이후에도 원본 변수를 사용할 수 있습니다.
}
만약 MyStruct가 가변 크기 데이터를 갖는 경우 Copy
trait을 구현할 수 없기 떄문에 #[derive(Copy)]
는 에러를 유발한다.
1
2
3
4
5
6
struct MyStruct {
x: i32,
y: i32,
name: String // 빌드 에러 유발 - Copy trait 불가
}
만약 사용자정의 struct에 Copy trait을 구현하지 않는다면, =
을 통해서 copy가 이뤄지지 않습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
#[derive(Debug, Clone)]
struct MyStruct {
x: i32,
y: i32,
// name: String
}
fn main() {
let original = MyStruct { x: 10, y: 20 };
let copied = original; // 이동 발생
println!("{:?}", original); // 컴파일 에러. 복사 이후에도 원본 변수를 사용할 수 없음
}
깊은 복사_와 _얕은 복사
얕은 복사는 데이터 구조의 복사본이 원본과 동일한 메모리를 공유하며, 내부 객체는 참조로 복사되어 변경 시 동일한 객체가 변경될 수 있습니다. 반면에 깊은 복사는 복사본이 원본과는 다른 메모리를 사용하며, 내부 객체가 복제되어 새로운 객체가 생성되어 원본과 독립적으로 존재합니다. C,C++,Java와 같은 언어에서 Vector(or List)와 같은 자료구조에 대해 대입 혹은 clone method를 호출 할 경우 기본적으로 레퍼런스만 복제되는 얕은 복사가 이뤄집니다. 이 경우 복제된 변수의 수정은 원본 data의 수정을 유발합니다. 반면 Rust에서는 대입연산(=
)은 소유권 이동을 의미하기 때문에 이러한 얕은 복사가 발생할 여지가 없고, clone trait 역시 다른 언어와 다르게 깊은 복사를 유발합니다. C++에서 깊은 복사와 얕은복사로 인해 소멸자 호출등으로 비정상적인 메모리 접근이 발생하거나 이를 방지하기 위해 다양한 테크닉을 써야하는데, rust는 언어적으로 훨씬 간편하게 이를 해결했다고 볼 수 있습니다.
깊은 복사 예제
#[derive(Clone)]
메크로를 통해 구현된 코드는 해당 타입의 필드가 구현된 Clone
트레이트를 사용하여 복제할 수 있습니다. Vec<String>
과 같이 여러 개의 String
을 포함하는 컬렉션을 필드로 갖는 경우는 #[derive(Clone)]
선언 만으로 깊은 복사가 이뤄 집니다. Clone
트레이트의 기본 동작은 깊은 복사(deep copy)를 수행합니다. 즉, Vec
가 복제될 때는 해당 Vec
의 요소들에 대해 clone
이 호출되며 새로운 복사본이 생성됩니다.
따라서 Vec<String>
을 포함하는 사용자 정의 타입에 대한 Clone
구현은 깊은 복사를 수행하는 것을 알 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#[derive(Debug, Clone)]
struct MyStruct {
age: i32,
data: Vec<String>,
}
fn main() {
let original = MyStruct {
age: 10,
data: vec![String::from("hello"), String::from("world")],
};
// 깊은 복사 수행
let copied = original.clone();
// 원본과 복사본이 동일한 메모리를 가리키고 있음을 확인하기 위해 주소 출력
println!("Original: {:p}", &original.data); // 0xb4ffb2f690
println!("Copied: {:p}", &copied.data); // 0xb4ffb2f708
println!("===================================");
println!("Original: {:p}", &original.age); // 0xb4ffb2f6a8
println!("Copied: {:p}", &copied.age); // 0xb4ffb2f720
println!("Original: {:?}, Copied: {:?}", original, copied); // Original: MyStruct { age: 10, data: ["hello", "world"] }, Copied: MyStruct { age: 10, data: ["hello", "world"] }
original.data.push(String::from("123"));
copied.data.push(String::from("456"));
println!("Original: {:?}, Copied: {:?}", original, copied); //Original: MyStruct { age: 10, data: ["hello", "world", "123"] }, Copied: MyStruct { age: 10, data: ["hello", "world", "456"] }
}
소유권과 Clone
trait를 통해 rust에서 C++에서 실수하기 쉬운 객체 이동/복사 부분을 간단히 해결한 것을 알 수 있습니다. 참고로 C++에서는 복사 생성자, 복사 대입 연산자, rlavue
, rvalue 참조
, move semantics
등 개념을 통해 이런 문제를 해결하는데, Rust는 언어적으로 이를 극복한 것 같습니다.