第5章: 参照と借用

学習目標

  • 参照の基本概念を理解する
  • 借用のルールを把握する
  • 可変参照と不変参照の違いを学ぶ
  • ダングリング参照を防ぐ方法を知る

---

5.1 参照とは何か?

5.1.1 所有権の問題を再確認

前章で学んだように、関数に値を渡すと所有権が移動します:

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

    // println!("{}", s); // ❌ エラー!sはムーブ済み
    println!("長さ: {}", len);
}

fn calculate_length(s: String) -> usize {
    s.len()
} // sがドロップされる

問題点

  • 値を使うたびに所有権を返さないといけない
  • コードが煩雑になる

5.1.2 参照による解決

参照(reference)を使えば、所有権を移動せずに値を使えます:

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);  // &s:sの参照

    println!("'{}' の長さは {}", s, len); // ✅ sはまだ使える!
}

fn calculate_length(s: &String) -> usize {
    s.len()
} // sは参照なので、実際の値はドロップされない

参照の構文

& → 参照を作成(borrow)
* → 参照を解決(dereference)

---

5.2 参照と借用

5.2.1 参照のメモリレイアウト

┌─────────────────────────────────────────────────────────┐
│              参照のメモリレイアウト                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  スタック                                                │
│  ┌────────────┐                                         │
│  │ s          │                                         │
│  │ ptr   ─────┼──┐                                      │
│  │ len   5    │  │       ヒープ                         │
│  │ cap   5    │  │       ┌─────────────┐               │
│  └────────────┘  └──────►│ h e l l o   │               │
│                          └─────────────┘               │
│  ┌────────────┐                                         │
│  │ r          │                                         │
│  │ ptr   ─────┼──┐                                      │
│  └────────────┘  │                                      │
│                  │                                      │
│                  └──────► sを指す(ヒープではなくsを指す)│
│                                                         │
└─────────────────────────────────────────────────────────┘

重要

  • 参照 rs を指す(ポインタのポインタのようなもの)
  • 参照は所有権を持たない
  • 参照がスコープを抜けても、元の値は影響を受けない

5.2.2 借用(Borrowing)

参照を作成することを借用(borrowing)と呼びます:

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

    borrow_string(&s);  // sを借用

    println!("{}", s);  // sはまだ所有者
}

fn borrow_string(some_string: &String) {
    println!("{}", some_string);
} // 借用が終わる(sには影響なし)

借用の比喩

本の所有権:
- 本を譲渡する(ムーブ)→ もう自分では読めない
- 本を貸す(借用)→ 返してもらえばまた読める

---

5.3 不変参照のルール

5.3.1 複数の不変参照

ルール:同時に複数の不変参照を作れます

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

    let r1 = &s;
    let r2 = &s;
    let r3 = &s;

    println!("{}, {}, {}", r1, r2, r3); // ✅ OK
}

なぜ安全?

  • どの参照も値を変更しない
  • データ競合の心配がない

5.3.2 不変参照では変更できない

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

    // r.push_str(", world"); // ❌ エラー!不変参照では変更不可
}

エラーメッセージ

error[E0596]: cannot borrow `*r` as mutable, as it is behind a `&` reference

---

5.4 可変参照

5.4.1 可変参照の作成

値を変更したい場合は可変参照(mutable reference)を使用:

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

    change(&mut s);  // &mut:可変参照

    println!("{}", s); // "hello, world"
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

要件

  • 元の変数が mut で宣言されている
  • &mut で可変参照を作成

5.4.2 可変参照の制約

重要なルール

┌─────────────────────────────────────────────────────────┐
│          可変参照の排他制御ルール                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  同じスコープで、同じ値に対して:                         │
│                                                         │
│  ✅ 複数の不変参照                                       │
│  ✅ 1つの可変参照のみ                                    │
│  ❌ 可変参照と不変参照の混在                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

例1:複数の可変参照(エラー)

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

    let r1 = &mut s;
    let r2 = &mut s; // ❌ エラー!2つ目の可変参照

    println!("{}, {}", r1, r2);
}

エラー

error[E0499]: cannot borrow `s` as mutable more than once at a time

なぜ?

  • データ競合を防ぐため
  • 同時に複数の場所から変更されるのは危険

例2:不変参照と可変参照の混在(エラー)

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

    let r1 = &s;     // 不変参照
    let r2 = &s;     // 不変参照
    let r3 = &mut s; // ❌ エラー!可変参照

    println!("{}, {}, {}", r1, r2, r3);
}

エラー

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable

なぜ?

  • 不変参照の使用者は「値が変わらない」と期待している
  • 可変参照が値を変更すると、その期待が裏切られる

5.4.3 スコープを利用した回避

スコープを分ければOK:

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

    {
        let r1 = &mut s;
        r1.push_str(" world");
    } // r1がスコープを抜ける

    let r2 = &mut s; // ✅ OK
    r2.push_str("!");

    println!("{}", s); // "hello world!"
}

5.4.4 Non-Lexical Lifetimes (NLL)

Rust 2018以降、参照のスコープは最後に使われた場所まで:

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

    let r1 = &s;
    let r2 = &s;
    println!("{}, {}", r1, r2);
    // r1とr2はここで最後に使用される

    let r3 = &mut s; // ✅ OK(r1, r2はもう使われない)
    println!("{}", r3);
}

古いRust(Rust 2015)では

  • 参照のスコープは宣言から}まで
  • 上記のコードはエラーになる

---

5.5 ダングリング参照

5.5.1 ダングリング参照とは

ダングリング参照(dangling reference): 無効なメモリを指す参照(C/C++の代表的なバグ)

C言語の例(危険)

int* dangle() {
    int x = 5;
    return &x;  // xはスコープを抜けて破棄される
}               // 返される参照は無効なメモリを指す

5.5.2 Rustの保護

Rustはコンパイル時にダングリング参照を防ぎます:

fn dangle() -> &String {  // ❌ コンパイルエラー
    let s = String::from("hello");
    &s
} // sがドロップされる → sへの参照は無効

エラー

error[E0106]: missing lifetime specifier
  --> src/main.rs:1:16
   |
1 | fn dangle() -> &String {
   |                ^ expected named lifetime parameter
   |
   = help: this function's return type contains a borrowed value,
           but there is no value for it to be borrowed from

修正方法:所有権を返す

fn no_dangle() -> String {  // ✅ OK
    let s = String::from("hello");
    s  // 所有権を移動
}

---

5.6 参照の実践パターン

5.6.1 読み取り専用アクセス

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

fn main() {
    let my_string = String::from("hello world");
    let word = first_word(&my_string);
    println!("最初の単語: {}", word);
}

5.6.2 変更を伴う処理

fn capitalize(s: &mut String) {
    if let Some(first_char) = s.chars().next() {
        let rest = s.split_off(1);
        s.clear();
        s.push(first_char.to_ascii_uppercase());
        s.push_str(&rest);
    }
}

fn main() {
    let mut text = String::from("hello");
    capitalize(&mut text);
    println!("{}", text); // "Hello"
}

5.6.3 複数の参照を返す

fn split_at_index(s: &String, index: usize) -> (&str, &str) {
    (&s[..index], &s[index..])
}

fn main() {
    let s = String::from("hello world");
    let (first, second) = split_at_index(&s, 5);
    println!("前半: {}, 後半: {}", first, second);
}

---

5.7 スライス型

5.7.1 文字列スライス

文字列スライス(&str):Stringの一部への参照

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

    let hello = &s[0..5];  // "hello"
    let world = &s[6..11]; // "world"

    // 省略形
    let hello = &s[..5];   // 先頭から
    let world = &s[6..];   // 末尾まで
    let whole = &s[..];    // 全体
}

メモリレイアウト

s: String
┌─────────────────┐
│ ptr ─────────┐  │
│ len   11     │  │
└──────────────┼──┘
               │
               ▼
         ┌─────────────────────┐
         │ h e l l o   w o r l d│
         └─────────────────────┘
              ▲           ▲
              │           │
         &s[..5]      &s[6..]

5.7.2 配列スライス

fn main() {
    let a = [1, 2, 3, 4, 5];

    let slice = &a[1..3]; // [2, 3]

    println!("{:?}", slice);
}

let a: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];
//         ^^^^^^ スライス型

---

5.8 参照のベストプラクティス

5.8.1 関数の引数

原則

┌─────────────────────────────────────────────────────────┐
│           関数引数の選択ガイド                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  読み取りのみ   → &T                                     │
│  変更が必要     → &mut T                                 │
│  所有権が必要   → T                                      │
│                                                         │
└─────────────────────────────────────────────────────────┘

// ✅ 良い:読み取りのみ
fn print_length(s: &String) {
    println!("長さ: {}", s.len());
}

// ✅ 良い:変更が必要
fn append_world(s: &mut String) {
    s.push_str(" world");
}

// ✅ 良い:所有権が必要
fn consume_string(s: String) {
    // sを消費する処理
}

// ❌ 悪い:読み取りだけなのに所有権を取る
fn print_length_bad(s: String) {
    println!("長さ: {}", s.len());
} // sがドロップされる(無駄)

5.8.2 Stringと&strの使い分け

// ✅ 良い:&strを受け取る(String、&str両方受け入れ可能)
fn process_text(s: &str) {
    println!("{}", s);
}

fn main() {
    let string = String::from("hello");
    let string_slice = "world";

    process_text(&string);      // String → &str
    process_text(string_slice); // &str → &str
}

// ❌ 悪い:Stringを受け取る(&strを受け入れられない)
fn process_text_bad(s: String) {
    println!("{}", s);
}

---

5.9 実践例

例1:文字列の最初の単語を取得

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[..i];
        }
    }

    &s[..]
}

fn main() {
    let my_string = String::from("hello world");
    let word = first_word(&my_string);

    // my_string.clear(); // ❌ エラー!wordが不変参照を保持中

    println!("最初の単語: {}", word);
}

例2:配列の最大値を見つける

fn find_max(numbers: &[i32]) -> Option<&i32> {
    if numbers.is_empty() {
        return None;
    }

    let mut max = &numbers[0];
    for num in &numbers[1..] {
        if num > max {
            max = num;
        }
    }

    Some(max)
}

fn main() {
    let numbers = [3, 1, 4, 1, 5, 9, 2, 6];

    match find_max(&numbers) {
        Some(max) => println!("最大値: {}", max),
        None => println!("空の配列"),
    }
}

例3:構造体と参照

struct User {
    name: String,
    age: u32,
}

impl User {
    // 読み取り専用メソッド
    fn display(&self) {
        println!("{} ({}歳)", self.name, self.age);
    }

    // 変更メソッド
    fn celebrate_birthday(&mut self) {
        self.age += 1;
        println!("誕生日おめでとう!");
    }

    // 消費メソッド
    fn into_name(self) -> String {
        self.name
    }
}

fn main() {
    let mut user = User {
        name: String::from("Alice"),
        age: 30,
    };

    user.display();               // &self
    user.celebrate_birthday();    // &mut self
    user.display();

    let name = user.into_name();  // self(消費)
    // user.display(); // ❌ エラー!userは消費された
    println!("名前: {}", name);
}

---

5.10 まとめ

学んだこと

  • 参照の基本
- &T:不変参照 - &mut T:可変参照 - 所有権を移動せずに値を使える

  • 借用のルール
- 複数の不変参照はOK - 可変参照は1つだけ - 可変参照と不変参照は混在不可

  • ダングリング参照の防止
- コンパイラが自動的に検出 - 無効な参照を作れない

  • スライス
- 配列や文字列の一部への参照 - &str&[T]

次のステップ

次の章では、ライフタイムを学びます: