導入
「いや、s1 も s2 も両方使いたいんだ」という場面もあります。そのときは、ヒープのデータごとまるごと複製すればよいのです。それが 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" を新しく確保して丸ごとコピーします。結果、s1 と s2 はそれぞれ独立したヒープデータを所有します。だから両方使えますし、二重解放も起きません(別々のヒープなので、それぞれが自分の分を解放する)。
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();):高い(ヒープごと複製)。でもs1もs2も使える。
clone() は明示的に書く必要があります。「ここでコストのかかる複製が起きているぞ」がコード上ひと目で分かるようにするためです。何気ない代入が裏で重いコピーをする、という隠れたコストがありません。
一方、第3章で触れた Copy 型(i32 などスタック完結型)は、そもそもヒープを持たないので clone() を書かなくても代入でまるごと複製され、元も使えます。
fn main() {
let a = 5;
let b = a; // Copy 型なので自動で複製。ムーブではない
println!("{a} {b}"); // 両方使える → 5 5
}
まとめ:ヒープを持つ型は既定でムーブ(元は無効)、複製したいなら明示的に clone()。スタック完結の Copy 型は代入で自動コピー(元も有効)。この線引きが分かれば、所有権の半分は理解できたようなものです。
試すには
ムーブでエラーになったコードに .clone() を足すと解決します。ただし clone() はヒープを複製するコストがあるので、「本当に2つ必要か?」を考える習慣を。多くの場合、次章の借用を使えば clone() せずに済みます。