04. 所有権: Rust 最大の特徴

🎯 この章のゴール

  • 所有権の 3 ルールを言えるようになる
  • ムーブ・クローン・コピー の違いを理解する

💡 ひとことで言うと

値は 一冊の本、変数は 持ち主。本を誰かに渡したら、自分の手元にはもうありません。持ち主が部屋(スコープ)を出るとき、本は片付けられます。

なぜ所有権?

メモリ管理の方式はだいたい 2 つでした。

  • 手動管理(C/C++): 速いが、解放忘れ・二重解放のバグが起きやすい
  • GC(Java, Go など): 安全だが、実行時のコストがかかる

Rust はどちらとも違い、コンパイル時に所有権ルールをチェックすることで、GC なしで安全性を保証します。

3 つのルール

  1. 各値には 所有者 と呼ばれる変数がある
  2. 所有者は 同時に 1 つだけ
  3. 所有者がスコープを抜けると、値は 破棄(drop) される
fn main() {
    {
        let s = String::from("hello"); // s が所有者
        println!("{s}");
    } // ← ここで s がスコープを抜け、メモリが解放される
}
Playground で開く ↗

ムーブ: 所有権の移動

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;          // 所有権が s2 へムーブ
    println!("{s1}");     // error[E0382]: borrow of moved value: `s1`
}
Playground で開く ↗

String はヒープ上にデータを持ちます。もし s1 と s2 の両方が同じデータを所有したら、スコープ終了時に 二重解放 が起きてしまいます。だから Rust は s1 を無効にするのです。

 スタック            ヒープ
 s1 (無効) ─┐
            ├──▶ [h][e][l][l][o]
 s2 ────────┘

クローン: 明示的な複製

ヒープのデータごと複製したいときは clone() を呼びます。重い複製は必ず clone() と書くことになるので、どこでコストがかかるかがコードを読めば分かります。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{s1} {s2}"); // どちらも使える
}
Playground で開く ↗

コピー: スタックだけの型

整数・浮動小数点・bool・char、それらだけからなるタプルなどは Copy トレイトを持ち、代入しても元の変数が使えます。

fn main() {
    let x = 5;
    let y = x; // コピー
    println!("{x} {y}");
}
Playground で開く ↗

関数と所有権

関数に渡す・関数から返すのも、ムーブ(またはコピー)です。

fn take(s: String) {
    println!("{s}");
} // s はここで drop

fn give() -> String {
    String::from("gift") // 所有権を呼び出し元へ返す
}

fn main() {
    let a = String::from("hi");
    take(a);            // a はムーブ済み。以降 a は使えない
    let b = give();     // b が新しい所有者
    println!("{b}");
}
Playground で開く ↗

毎回所有権を渡して返してもらうのは面倒ですよね。そこで登場するのが次章の 参照(借用) です。

🧪 やってみよう

下の書きかけコードの TODO を埋めて、▶ 実行 で確かめよう。

まずはこのまま ▶ 実行 して、エラーメッセージを読んでみましょう。Rust はどこでムーブが起き、どこで使おうとしたかを矢印で示してくれます。読めたら clone() を使って直してみよう。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // TODO: s1 も使えるように直そう
    println!("{s1} {s2}");
}
Playground で開く ↗
💡 解答例を見る
fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{s1} {s2}");
}
Playground で開く ↗

📝 まとめ

  • 値の所有者は常に 1 つ。スコープを抜けると自動で解放
  • ヒープを持つ型(String, Vec など)は代入・関数渡しで ムーブ
  • 複製したいなら clone()、Copy 型は自動でコピー

✅ 理解度クイズ

Q1
次のコードはコンパイルできる?
fn main() {
    let s1 = String::from("hi");
    let s2 = s1;
    println!("{s1}");
}
Q2
次のコードはコンパイルできる?
fn main() {
    let a = 10;
    let b = a;
    println!("{a} {b}");
}
Q3
所有権の 3 つのルールに 含まれない ものは?
Q4
関数 fn take(s: String) {} に take(name) と渡した後、name はどうなる?