導入
同じ「変数」でも、スタックだけで完結する値と、ヒープを使う値では、Rust での振る舞いがまったく変わります。この違いが、次章の「ムーブ」と「コピー」の分かれ道になります。まずは完全にスタックで完結する型から。
説明
i32 や bool、char、それらだけからなるタプルや固定長配列は、大きさがコンパイル時に決まっているので、まるごとスタックに置かれます。こうした型を代入すると、値はまるごと複製(コピー) されます。
fn main() {
let a = 5;
let b = a; // a の値(5)がコピーされる。a も b も別々に 5 を持つ
println!("a = {a}, b = {b}"); // どちらも使える → a = 5, b = 5
}
b = a で「5」というスタック上の値がそっくり複製されます。a と b は独立した2つの 5 です。だから代入後も両方そのまま使えます。図にするとこうです。
graph TD
subgraph stack["スタック"]
a["a: 5"]
b["b: 5(a とは別の複製)"]
end
このように「代入や関数渡しで自動的に丸ごと複製される」型を、Rust では Copy 型(Copy トレイトを持つ型)と呼びます。スタックだけで完結し、複製が安くて安全だからです。
Copyになる代表:i32/u32/f64などの数値、bool、char、Copyな型だけのタプル(例:(i32, i32))、固定長配列。Copyにならない代表:String、Vec<T>など、ヒープを使う型。これらは丸ごと複製すると高コスト、かつ「同じヒープを2人が持つ」危険があるため、単純コピーはしません。ここで次のレッスンのStringが登場します。
つまり Rust では、「その型がヒープを持つかどうか」で代入時の挙動が変わります。スタック完結型は気軽にコピー、ヒープを持つ型は特別扱い——この線引きが所有権のすべての出発点です。
試すには
let a = 5; let b = a; println!("{a}"); は問題なく動きます。次のレッスンで、これを String に置き換えると挙動が一変することを確認します。「数値だと平気なのに、なぜ文字列だとダメなのか」を意識しておいてください。