Menu
Coddy logo textTech

Clone と Copy

CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 37/61。

Rustでは、値を別の変数に代入すると、通常、所有権が移動(ムーブ)し、元の変数は無効になります。しかし、2つの標準的なトレイトがこの挙動を変更します:CloneCopy です。

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() による明示的複製可能なあらゆるデータ

プリミティブなフィールドを持つシンプルな構造体の場合、両方のトレイトを派生させることで、所有権を気にすることなく自由に値を代入できるようになります。

challenge icon

チャレンジ

簡単

Copy トレイトと Clone トレイトの違いを実証する座標システムを構築しましょう!暗黙的にコピーできる構造体と、明示的なクローンが必要な構造体の2つを作成し、派生させるトレイトに基づいて Rust がどのように複製の処理を変えるかを確認します。

コードは2つのファイルに分けて構成します:

  • coordinates.rs: 異なる種類の座標を表す2つの公開構造体を定義します:
    • GridPoint — 公開フィールド xy(どちらも i32)を持ちます。プリミティブ型のみを含むため、CopyClone の両方を派生させ、代入時に暗黙的にコピーできるようにします。
    • 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})

例えば、入力が 510Home の場合:

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})"
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン