Clone と Copy
CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 37/61。
Rustでは、値を別の変数に代入すると、通常、所有権が移動(ムーブ)し、元の変数は無効になります。しかし、2つの標準的なトレイトがこの挙動を変更します: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()メソッドを通じて明示的なディープコピーを提供します。これはCopyを派生させる場合には常に必要ですが、明示的なコピーが必要な型に対して単独で使用することもできます。
let p3 = p1.clone(); // 明示的なコピー
重要な制限があります:Copy は、すべてのフィールドも Copy を実装している型に対してのみ派生させることができます。整数や浮動小数点数のような単純な型は Copy ですが、String はそうではありません。ヒープメモリを管理しているからです。構造体に String が含まれている場合、Copy ではなく Clone のみを派生させることができます。
| トレイト | 振る舞い | 用途 |
|---|---|---|
Copy | 暗黙的、自動的 | スタックのみに配置される単純なデータ |
Clone | .clone() による明示的 | 複製可能なあらゆるデータ |
プリミティブなフィールドを持つシンプルな構造体の場合、両方のトレイトを派生させることで、所有権を気にすることなく自由に値を代入できるようになります。
チャレンジ
簡単Copy トレイトと Clone トレイトの違いを実証する座標システムを構築しましょう!暗黙的にコピーできる構造体と、明示的なクローンが必要な構造体の2つを作成し、派生させるトレイトに基づいて Rust がどのように複製の処理を変えるかを確認します。
コードは2つのファイルに分けて構成します:
coordinates.rs: 異なる種類の座標を表す2つの公開構造体を定義します:GridPoint— 公開フィールドxとy(どちらもi32)を持ちます。プリミティブ型のみを含むため、CopyとCloneの両方を派生させ、代入時に暗黙的にコピーできるようにします。NamedLocation— 公開フィールドname(String) とx/y(i32) を持ちます。Stringを含んでいるため、Cloneのみを派生させ、Copyは派生させられません。この構造体を複製するには、明示的な.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)3つの入力を受け取ります:x 座標(i32 としてパース)、y 座標(i32 としてパース)、および場所の名前です。
チートシート
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() メソッドを通じて明示的なディープコピーを提供します:
let p3 = p1.clone(); // 明示的なコピー
Copy は、すべてのフィールドも Copy を実装している型に対してのみ派生(derive)できます。整数や浮動小数点数のような単純な型は Copy ですが、String はそうではありません。構造体に String が含まれている場合、Clone のみを派生させることができ、Copy は派生させられません。
| トレイト | 振る舞い | 用途 |
|---|---|---|
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: 別の変数に代入することで暗黙的なコピーを実演する
// (GridPoint は Copy を実装しているため、自動的にコピーが作成されます)
// TODO: 元のポイントとコピーされたポイントを表示する
// フォーマット: "Original point: ({x}, {y})"
// フォーマット: "Copied point: ({x}, {y})"
// TODO: name、x、y を使用して NamedLocation インスタンスを作成する
// TODO: 明示的なクローンを実演する (NamedLocation は Clone のみを実装し、Copy は実装していません)
// 複製を作成するために .clone() を使用する
// TODO: 元のロケーションとクローンされたロケーションを表示する
// フォーマット: "Original location: {name} at ({x}, {y})"
// フォーマット: "Cloned location: {name} at ({x}, {y})"
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。