Clone と Copy
CoddyのRustジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 37/61。
Rustでは、値をanother変数に代入すると、通常は所有権が移動し、元の変数は無効になります。ただし、2つの標準トレイト、CloneとCopyによって、この動作は変わります。
Copy トレイトは暗黙的なビット単位のコピーを可能にします。型が Copy を実装すると、それを another 変数に assigning することで、所有権を移動する代わりに自動的なコピーが作成されます。
#[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トレイトは、explicit なディープコピーを.clone()メソッドによって提供します。Copyをderiveする場合は必須ですが、明示的なコピーが必要な型では単独で使用することもできます。
let p3 = p1.clone(); // 明示的なコピー
重要な制限があります。すべてのフィールドも Copy を implement している型に対してのみ、Copy を derive できます。整数や浮動小数点数のような単純な型は Copy ですが、String はそうではありません。String はヒープメモリを管理します。構造体に String が含まれている場合、derive できるのは Clone だけで、Copy は derive できません。
| Trait | 動作 | 用途 |
|---|---|---|
Copy | 暗黙的、自動 | スタックのみの単純なデータ |
Clone | .clone() による明示的な複製 | 複製可能な任意のデータ |
プリミティブ型のフィールドを持つ単純な構造体では、両方のトレイトを導出すると、所有権を気にせずに値を自由に代入できます。
チャレンジ
簡単Copy と Clone トレイトの違いを示す座標系を作成しましょう!暗黙的にコピーできる構造体と、明示的なクローンが必要な構造体を 1 つずつ作成し、derive するトレイトによって Rust が複製を異なる方法で処理する様子を確認します。
コードを 2 つのファイルに分けて整理します。
coordinates.rs: 異なる種類の座標を表す 2 つの公開構造体を Define します。GridPoint: 公開xおよびyfields(どちらもi32)を持ちます。プリミティブ型のみを含むため、CopyとCloneの両方を derive し、代入時に暗黙的にコピーできるようにします。NamedLocation: 公開name(String)とx/yfields(i32)を持ちます。Stringを含むため、derive できるのはCloneだけで、Copyは derive できません。この構造体を複製するには、明示的な.clone()呼び出しが必要です。
main.rs: coordinates モジュールを取り込み、各構造体が複製時にどのように異なる動作をするかを示します。提供された入力を使用してインスタンスを作成し、次に以下を示します。GridPointは another 変数に代入でき、両方とも有効なままになる(暗黙的なコピー)NamedLocationは、元の値を有効なままにして duplicate を作成するために.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 として parse)、y 座標(i32 として parse)、および location name です。
自分で試してみよう
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: 元のポイントとコピーしたポイントを出力する
// Format: "Original point: ({x}, {y})"
// 形式: "Copied point: ({x}, {y})"
// TODO: name、x、y で NamedLocation のインスタンスを作成する
// TODO: 明示的なクローンを示す(NamedLocation は Clone のみを実装し、Copy は実装していない)
// .clone() を使用して複製を作成する
// TODO: Print original and cloned location
// Format: "Original location: {name} at ({x}, {y})"
// Format: "Cloned location: {name} at ({x}, {y})"
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rustオンラインコンパイラ