第4章: 所有権入門

学習目標

  • 所有権の基本ルールを理解する
  • スコープとドロップの仕組みを学ぶ
  • ムーブセマンティクスを把握する
  • クローンとコピーの違いを知る
  • ---

    4.1 所有権とは何か?

    4.1.1 メモリ管理の歴史

    ┌─────────────────────────────────────────────────────────┐
    │          プログラミング言語のメモリ管理戦略               │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  【手動管理】              【自動管理】                  │
    │   C/C++                    Java/Python/Go               │
    │                                                         │
    │   malloc/free              Garbage Collection           │
    │   new/delete                                            │
    │                                                         │
    │   ✓ 高速                   ✓ 安全                       │
    │   ✓ 予測可能               ✓ 生産性高い                 │
    │   ✗ バグが多い             ✗ GCポーズ                   │
    │   ✗ 危険                   ✗ メモリオーバーヘッド        │
    │                                                         │
    │                                                         │
    │  【Rustのアプローチ】                                    │
    │   所有権システム                                         │
    │                                                         │
    │   ✓ 高速(GCなし)                                       │
    │   ✓ 安全(コンパイル時保証)                             │
    │   ✓ 予測可能                                             │
    │   ✗ 学習曲線が急                                         │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    4.1.2 所有権の3つのルール

    Rustの所有権システムは3つのシンプルなルールに基づいています:

    ┌─────────────────────────────────────────────────────────┐
    │               所有権の3つの黄金律                         │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  1️⃣ すべての値には、所有者(owner)が存在する              │
    │                                                         │
    │  2️⃣ 所有者は同時に1つだけ                                │
    │                                                         │
    │  3️⃣ 所有者がスコープを抜けると、値は破棄される             │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    ---

    4.2 スコープと所有権

    4.2.1 変数のスコープ

    fn main() {
        // sはまだ有効ではない(宣言前)
    
        let s = "hello";   // sがスコープに入る
                           // ここから使用可能
    
        // sを使用
        println!("{}", s);
    
    } // sがスコープを抜ける → 自動的に破棄
    

    4.2.2 String型とヒープメモリ

    スタック vs ヒープ

    ┌─────────────────────────────────────────────────────────┐
    │              スタック vs ヒープ                          │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  【スタック】                 【ヒープ】                 │
    │                                                         │
    │  高速                         遅い                       │
    │  固定サイズ                   可変サイズ                 │
    │  自動管理                     手動管理(Rustは自動)     │
    │  LIFO(後入れ先出し)         ランダムアクセス            │
    │                                                         │
    │  例:整数、bool、固定配列     例:String、Vec、Box       │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    String型の内部構造

    let s = String::from("hello");
    

    ┌───────────────┐
    │   スタック     │
    ├───────────────┤
    │ ptr  ────────┼──┐
    │ len    5      │  │   ┌─────────────────┐
    │ capacity 5    │  │   │     ヒープ       │
    └───────────────┘  │   ├─────────────────┤
                       └──►│ h e l l o       │
                           └─────────────────┘
    

  • ptr: ヒープメモリへのポインタ
  • len: 現在の長さ
  • capacity: 確保されている容量

---

4.3 ムーブセマンティクス

4.3.1 所有権の移動(Move)

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;  // s1の所有権がs2に移動(ムーブ)

    println!("{}", s2); // ✅ OK
    println!("{}", s1); // ❌ エラー!s1は無効
}

エラーメッセージ

error[E0382]: borrow of moved value: `s1`
  --> src/main.rs:5:20
   |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println!("{}", s1);
  |                    ^^ value borrowed here after move

内部で何が起きているか

ステップ1:s1が作成される
┌───────┐
│ s1    │
│ ptr ──┼──► "hello"
│ len 5 │
└───────┘

ステップ2:s2 = s1(シャローコピー + s1無効化)
┌───────┐
│ s1    │ ← 無効!
│ ×××   │
└───────┘
┌───────┐
│ s2    │
│ ptr ──┼──► "hello"
│ len 5 │
└───────┘

なぜムーブが必要?

もしディープコピーだったら:

  • 毎回メモリをコピー → 遅い
  • 不要なメモリ使用

もしシャローコピーだけだったら:

  • 二重解放(double free)の危険
// C++なら問題が発生
let s1 = String::from("hello");
let s2 = s1;  // シャローコピー
// スコープを抜ける時、s1とs2の両方が同じメモリを解放しようとする!

Rustの解決:ムーブ(所有権の移動)

  • シャローコピー + 元の変数を無効化
  • 常に1つの所有者のみ
  • 二重解放の問題なし

4.3.2 関数と所有権

値を関数に渡すと所有権が移動

fn main() {
    let s = String::from("hello");

    takes_ownership(s);  // sの所有権が関数に移動

    // println!("{}", s); // ❌ エラー!sは無効
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
} // some_stringがスコープを抜ける → メモリ解放

関数からの戻り値で所有権を返す

fn main() {
    let s1 = gives_ownership();  // 所有権を受け取る

    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);  // s2を渡し、戻り値でs3に

    // s1とs3は有効、s2は無効
}

fn gives_ownership() -> String {
    let some_string = String::from("yours");
    some_string  // 所有権を返す
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string  // 受け取った所有権を返す
}

---

4.4 クローン

4.4.1 明示的なディープコピー

本当にデータをコピーしたい場合は clone() を使用:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // ディープコピー

    println!("s1 = {}, s2 = {}", s1, s2); // ✅ 両方有効
}

cloneの動作

┌───────┐                  ┌───────┐
│ s1    │                  │ s2    │
│ ptr ──┼──► "hello"       │ ptr ──┼──► "hello"(コピー)
│ len 5 │                  │ len 5 │
└───────┘                  └───────┘

cloneは高コスト

  • ヒープメモリの割り当て
  • データのコピー
  • 明示的に呼ぶことでコストを可視化

let s1 = String::from("very long string...");
let s2 = s1.clone();  // 「ここでコピーが発生している」と明示

---

4.5 Copy トレイト

4.5.1 スタックのみのデータ

整数など、スタックに格納されるデータは自動的にコピーされます:

fn main() {
    let x = 5;
    let y = x;  // コピー(ムーブではない)

    println!("x = {}, y = {}", x, y); // ✅ 両方有効
}

なぜ?

整数は固定サイズ → スタックに格納
コピーが高速 → ムーブにする意味がない

4.5.2 Copy トレイトを持つ型

以下の型は Copy トレイトを実装しており、自動的にコピーされます:

// スカラー型
let x: i32 = 5;
let y: u64 = 100;
let z: f64 = 3.14;
let b: bool = true;
let c: char = 'a';

// タプル(すべての要素がCopyの場合)
let t: (i32, i32) = (1, 2);

// 固定長配列(要素がCopyの場合)
let a: [i32; 3] = [1, 2, 3];

Copyトレイトを持たない型

// String、Vec、Boxなど、ヒープを使用する型
let s = String::from("hello");  // Copyではない → ムーブ

// タプルや配列でも、要素にCopyでない型があればCopyでない
let t = (String::from("hello"), 5);  // Copyではない

4.5.3 Copyとムーブの判定

┌─────────────────────────────────────────────────────────┐
│              型の動作の判定フロー                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   型はCopyトレイトを実装している?                        │
│        │                                                │
│        ├─ Yes → コピー(元の変数も有効)                 │
│        │                                                │
│        └─ No  → ムーブ(元の変数は無効)                 │
│                                                         │
└─────────────────────────────────────────────────────────┘

実践例

fn main() {
    // Copyトレイト:コピーされる
    let x = 5;
    let y = x;
    println!("x = {}, y = {}", x, y); // ✅

    // Copyなし:ムーブ
    let s1 = String::from("hello");
    let s2 = s1;
    // println!("{}", s1); // ❌ エラー

    // 明示的なクローン
    let s3 = String::from("world");
    let s4 = s3.clone();
    println!("s3 = {}, s4 = {}", s3, s4); // ✅
}

---

4.6 所有権と関数

4.6.1 関数の引数

fn main() {
    let s = String::from("hello");

    takes_ownership(s);
    // sはもう使えない

    let x = 5;
    makes_copy(x);
    // xはまだ使える(Copyトレイト)
    println!("x = {}", x);
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
} // some_stringがドロップされる

fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
} // some_integerがドロップされる(でも元の値は影響なし)

4.6.2 戻り値と所有権

fn main() {
    let s1 = gives_ownership();
    println!("{}", s1);

    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);
    // s2は無効、s3は有効
    println!("{}", s3);
}

fn gives_ownership() -> String {
    String::from("yours")
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string
}

4.6.3 複数の値を返す(タプル)

fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("'{}' の長さは {} です", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)  // タプルで所有権を返す
}

これは面倒... 次の章で参照を学びます!

---

4.7 実践例

例1:所有権の可視化

fn main() {
    let s1 = String::from("hello");
    println!("s1 = {}", s1);

    let s2 = s1;
    // println!("s1 = {}", s1); // エラー
    println!("s2 = {}", s2);

    let s3 = s2.clone();
    println!("s2 = {}, s3 = {}", s2, s3);
}

例2:関数での所有権

fn main() {
    let s = String::from("hello");
    let s = append_world(s);
    println!("{}", s);
}

fn append_world(mut s: String) -> String {
    s.push_str(", world");
    s
}

例3:整数とStringの違い

fn main() {
    // 整数:Copy
    let x = 5;
    let y = x;
    println!("x = {}, y = {}", x, y);

    // String:Move
    let s1 = String::from("hello");
    let s2 = s1;
    // println!("{}", s1); // エラー
    println!("{}", s2);
}

---

4.8 よくある間違いとその解決

間違い1:ムーブ後の使用

// ❌ エラー
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1);  // ムーブ後の使用

// ✅ 解決1:cloneを使う
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}, {}", s1, s2);

// ✅ 解決2:参照を使う(次の章で学習)

間違い2:関数に渡した後の使用

// ❌ エラー
let s = String::from("hello");
print_string(s);
println!("{}", s);  // sはムーブ済み

fn print_string(s: String) {
    println!("{}", s);
}

// ✅ 解決1:所有権を返す
let s = String::from("hello");
let s = print_and_return(s);
println!("{}", s);

fn print_and_return(s: String) -> String {
    println!("{}", s);
    s
}

// ✅ 解決2:参照を使う(次の章)

間違い3:ループ内でのムーブ

// ❌ エラー
let s = String::from("hello");
for _ in 0..3 {
    print_string(s);  // 1回目でムーブ、2回目でエラー
}

// ✅ 解決1:cloneする
let s = String::from("hello");
for _ in 0..3 {
    print_string(s.clone());
}

// ✅ 解決2:参照を使う(次の章)

---

4.9 まとめ

学んだこと

  • 所有権の3つのルール
- すべての値には所有者がいる - 所有者は1つだけ - スコープを抜けると破棄

  • ムーブセマンティクス
- Stringなどヒープを使う型はムーブ - 元の変数は無効になる

  • Copy トレイト
- 整数など固定サイズ型は自動コピー - ムーブではなくコピー

  • 関数と所有権
- 引数に渡すと所有権が移動 - 戻り値で所有権を返せる

次のステップ

所有権を理解したら、次は参照と借用を学びます。