導入
ヒープを使う型の一番身近な例が String です。所有権の説明はほぼ必ず String で行われます。まず「文字列リテラル(&str)」と「String」の違い——スタックに載る文字列とヒープを持つ文字列の違いを押さえましょう。
説明
Rust の文字列には大きく2種類あります。
① 文字列リテラル &str — プログラムに直接書いた "hello" のような文字列。中身はコンパイル時に確定していて変更できません。実行ファイルに埋め込まれ、変数はその場所を指すだけです。サイズ固定・不変で、軽量です。
let s: &str = "hello"; // 変更不可。中身はプログラムに埋め込み済み
② String — ヒープ上に確保される、伸び縮みできる文字列。ユーザー入力を受けたり、後から文字を足したりできます。
fn main() {
let mut s = String::from("hello"); // ヒープに "hello" を確保
s.push_str(", world"); // 後から追記できる(伸びる)
println!("{s}"); // hello, world
}
String の正体を見てみましょう。String という変数は、実はスタック上に3つの情報を持っています。
- ポインタ:ヒープ上の文字データを指す住所。
- 長さ(len):いま何バイト使っているか。
- 容量(capacity):ヒープに確保してある広さ。
そして実際の文字データ(h e l l o)はヒープにあります。
graph LR
subgraph stack["スタック(String 本体)"]
ptr["ptr ●"]
len["len: 5"]
cap["cap: 5"]
end
subgraph heap["ヒープ(実際の文字データ)"]
d["h | e | l | l | o"]
end
ptr -->|指す| d
この「スタックに管理情報(ポインタ+長さ+容量)、ヒープに実データ」という二層構造は、String だけでなく Vec(可変長配列)など多くの型に共通する、Rust の超重要パターンです。
ここが所有権につながる肝:String の変数 s は「ヒープ上の文字データの所有者」です。s がスコープ(有効範囲)を抜けるとき、Rust は自動的にヒープのデータを解放します。この「解放」は drop という特別な処理で、スコープの } の位置でコンパイラが自動的に呼び出します。
fn main() {
{
let s = String::from("hello"); // s がヒープを確保
// s を使う……
} // ← ここで s がスコープを抜ける。Rust が自動で drop(s) を挿入し、ヒープを解放
}
C言語の free() を自分で書く代わりに、Rust は「変数がスコープを抜けたら、その所有するヒープを解放する」を自動で・コンパイル時に組み込みます。GC のように実行中に探し回るのでもなく、人間が書き忘れるのでもない。この仕組みこそが、次章「所有権」の中身です。
試すには
String::from("hi") と "hi" の違いを意識しましょう。前者はヒープを確保し所有者が要る値、後者はプログラムに埋め込まれた不変の借り物です。let mut s = "hi"; s.push_str("!"); はエラー(&str は変更不可)、String::from にすれば通ります。この「所有する値かどうか」が、次章の主題です。