本文へスキップ
BecomeCoder

Rustコース · 第3章 メモリの土台 ― スタックとヒープ · レッスン14

ヒープを持つ型の代表 ― String と &str

ローカル実施

導入

ヒープを使う型の一番身近な例が 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 にすれば通ります。この「所有する値かどうか」が、次章の主題です。