Clone과 Copy
Coddy Rust 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 37번째.
Rust에서 값을 다른 변수에 할당할 때, 소유권은 일반적으로 이동하며 원래 변수는 무효화됩니다. 하지만 두 가지 표준 트레이트가 이 동작을 변경합니다: Clone 및 Copy.
Copy 트레이트는 암시적인 비트 단위 복사를 가능하게 합니다. 타입이 Copy를 구현하면, 이를 다른 변수에 할당할 때 소유권이 이동하는 대신 자동으로 복사본이 생성됩니다:
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // p1은 복사되며, 이동되지 않습니다
println!("{}, {}", p1.x, p2.x); // 둘 다 유효합니다!
Clone 트레이트는 .clone() 메서드를 통해 명시적 깊은 복사(explicit deep copying)를 제공합니다. Copy를 파생(derive)할 때마다 필요하지만, 명시적 복사가 필요한 타입에 대해 단독으로 사용될 수도 있습니다:
let p3 = p1.clone(); // 명시적 복사
중요한 제약 사항이 있습니다: Copy는 모든 필드가 Copy를 구현하는 타입에 대해서만 파생될 수 있습니다. 정수나 부동 소수점과 같은 단순한 타입은 Copy이지만, String은 그렇지 않습니다. String은 힙 메모리를 관리하기 때문입니다. 만약 구조체에 String이 포함되어 있다면, Copy가 아닌 Clone만 파생할 수 있습니다.
| 특성 | 동작 | 사용법 |
|---|---|---|
Copy | 암시적, 자동 | 단순 스택 전용 데이터 |
Clone | .clone()을 통한 명시적 호출 | 복제 가능한 모든 데이터 |
기본 타입 필드를 가진 단순한 구조체의 경우, 두 트레이트를 모두 파생(derive)하면 소유권에 대해 걱정하지 않고 자유롭게 값을 할당할 수 있습니다.
챌린지
쉬움Copy와 Clone 트레이트의 차이점을 보여주는 좌표 시스템을 만들어 봅시다! 암시적으로 복사할 수 있는 구조체와 명시적인 클로닝이 필요한 구조체, 이렇게 두 개의 구조체를 생성하여 Rust가 파생된 트레이트에 따라 복제를 어떻게 다르게 처리하는지 확인해 보겠습니다.
코드는 두 개의 파일로 구성됩니다:
coordinates.rs: 서로 다른 종류의 좌표를 나타내는 두 개의 공개(public) 구조체를 정의합니다:GridPoint— 공개 필드x와y(둘 다i32)를 가집니다. 기본 타입(primitive types)만 포함하므로Copy와Clone을 모두 파생(derive)시켜 할당 시 암시적으로 복사될 수 있도록 합니다.NamedLocation— 공개 필드name(String)과x/y(i32)를 가집니다.String을 포함하고 있으므로Copy는 불가능하며Clone만 파생시킬 수 있습니다. 이 구조체는 복제를 위해 명시적인.clone()호출이 필요합니다.
main.rs: coordinates 모듈을 가져와서 각 구조체가 복제될 때 어떻게 다르게 동작하는지 보여줍니다. 제공된 입력을 사용하여 인스턴스를 생성한 후 다음을 증명하세요:GridPoint는 다른 변수에 할당될 수 있으며 두 변수 모두 유효하게 남습니다 (암시적 복사)NamedLocation은 원본을 유효하게 유지하면서 복제본을 만들기 위해 반드시.clone()을 사용해야 합니다
출력은 복제 후에도 두 변수가 모두 유효함을 증명하기 위해 원본 값과 복사/클론된 값을 모두 보여주어야 합니다:
Original point: ({x}, {y})
Copied point: ({x}, {y})
Original location: {name} at ({x}, {y})
Cloned location: {name} at ({x}, {y})예를 들어, 입력값이 5, 10, Home인 경우:
Original point: (5, 10)
Copied point: (5, 10)
Original location: Home at (5, 10)
Cloned location: Home at (5, 10)세 개의 입력을 받게 됩니다: x 좌표(i32로 파싱), y 좌표(i32로 파싱), 그리고 장소 이름(location name)입니다.
치트 시트
Copy 트레이트는 암시적인 비트 단위 복사(implicit bitwise copying)를 가능하게 합니다. 어떤 타입이 Copy를 구현하면, 이를 다른 변수에 할당할 때 소유권이 이동(move)하는 대신 자동으로 복사본이 생성됩니다:
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 10, y: 20 };
let p2 = p1; // p1 is copied, not moved
println!("{}, {}", p1.x, p2.x); // Both are valid!
Clone 트레이트는 .clone() 메서드를 통해 명시적인 깊은 복사(explicit deep copying)를 제공합니다:
let p3 = p1.clone(); // Explicit copy
Copy는 모든 필드가 Copy를 구현하는 타입에 대해서만 파생(derive)될 수 있습니다. 정수나 부동 소수점과 같은 단순한 타입은 Copy이지만, String은 그렇지 않습니다. 만약 구조체에 String이 포함되어 있다면, Copy는 파생할 수 없고 Clone만 파생할 수 있습니다.
| 트레이트 | 동작 | 용도 |
|---|---|---|
Copy | 암시적, 자동 | 단순한 스택 전용 데이터 |
Clone | .clone()을 통한 명시적 실행 | 복제 가능한 모든 데이터 |
Copy를 파생할 때는 반드시 Clone도 함께 파생해야 합니다.
직접 해보기
mod coordinates;
use coordinates::{GridPoint, NamedLocation};
fn main() {
// 입력 읽기
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let x: i32 = input1.trim().parse().expect("Invalid number");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let y: i32 = input2.trim().parse().expect("Invalid number");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let name = input3.trim().to_string();
// TODO: x와 y를 사용하여 GridPoint 인스턴스 생성
// TODO: 다른 변수에 할당하여 암시적 복사(implicit copy) 시연
// (GridPoint는 Copy를 구현하므로, 자동으로 복사본이 생성됨)
// TODO: 원본 포인트와 복사된 포인트 출력
// 형식: "Original point: ({x}, {y})"
// 형식: "Copied point: ({x}, {y})"
// TODO: name, x, y를 사용하여 NamedLocation 인스턴스 생성
// TODO: 명시적 클론(explicit clone) 시연 (NamedLocation은 Copy가 아닌 Clone만 구현함)
// .clone()을 사용하여 복제본 생성
// TODO: 원본 위치와 클론된 위치 출력
// 형식: "Original location: {name} at ({x}, {y})"
// 형식: "Cloned location: {name} at ({x}, {y})"
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.