本文へスキップ
BecomeCoder

Rustコース · 第4章 所有権 · レッスン17

clone とコピー ― 本当に複製したいとき

ブラウザで完結

導入

「いや、s1s2 も両方使いたいんだ」という場面もあります。そのときは、ヒープのデータごとまるごと複製すればよいのです。それが clone() です。ムーブとの違いをはっきりさせましょう。

説明

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();      // ヒープのデータごと複製する(ディープコピー)

    println!("s1 = {s1}");    // OK! s1 も生きている
    println!("s2 = {s2}");    // OK! s2 も別のヒープを持つ
}

s1.clone() は、ヒープ上の "hello"新しく確保して丸ごとコピーします。結果、s1s2それぞれ独立したヒープデータを所有します。だから両方使えますし、二重解放も起きません(別々のヒープなので、それぞれが自分の分を解放する)。

graph LR
    subgraph stack["スタック"]
        s1["s1: ptr ●"]
        s2["s2: ptr ●"]
    end
    subgraph heap["ヒープ"]
        d1["h | e | l | l | o"]
        d2["h | e | l | l | o(別の複製)"]
    end
    s1 --> d1
    s2 --> d2
  • ムーブ(let s2 = s1;:安い(ポインタ等だけコピー)。でも s1 は無効化。
  • クローン(let s2 = s1.clone();:高い(ヒープごと複製)。でも s1s2 も使える。

clone() は明示的に書く必要があります。「ここでコストのかかる複製が起きているぞ」がコード上ひと目で分かるようにするためです。何気ない代入が裏で重いコピーをする、という隠れたコストがありません。

一方、第3章で触れた Copyi32 などスタック完結型)は、そもそもヒープを持たないので clone() を書かなくても代入でまるごと複製され、元も使えます。

fn main() {
    let a = 5;
    let b = a;            // Copy 型なので自動で複製。ムーブではない
    println!("{a} {b}");  // 両方使える → 5 5
}

まとめ:ヒープを持つ型は既定でムーブ(元は無効)、複製したいなら明示的に clone()。スタック完結の Copy 型は代入で自動コピー(元も有効)。この線引きが分かれば、所有権の半分は理解できたようなものです。

試すには

ムーブでエラーになったコードに .clone() を足すと解決します。ただし clone() はヒープを複製するコストがあるので、「本当に2つ必要か?」を考える習慣を。多くの場合、次章の借用を使えば clone() せずに済みます。

実際に動かしてみよう

下のエディタにRustを書いて「実行」を押すと、学習用シミュレータが println! の出力を表示します(本物のrustcではなく、教材の範囲を再現した軽量エンジンです)。struct・enum・match・トレイト・Vec/String/HashMap・Option/Result なども動きます。本文の例を書き換えて動かしてみましょう(所有権・借用チェッカーのコンパイルエラーは再現しないため、動きの確認用として使ってください)。

Rust — ブラウザ内で実行(学習用シミュレータ)

Rustの教材サブセットを動かす学習用シミュレータを読み込みます(本物のrustcではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。