Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

参照と借用

所有権を移動させずにデータを使う「参照」と「借用」について学びます。

問題: 所有権を移動させたくない

前回の例を思い出してください:

fn main() {
    let s = String::from("hello");
    let len = calculate_length(s);  // 所有権がムーブ
    // println!("{}", s);  // エラー!
}

fn calculate_length(s: String) -> usize {
    s.len()
}

値を使いたいだけなのに、所有権を渡さなければいけないのは不便です。

解決策: 参照(Reference)

参照を使うと、所有権を移動させずにデータにアクセスできます。

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);  // 参照を渡す
    println!("'{}'の長さは{}", s, len);  // sはまだ使える!
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

参照の図解

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

スタック                     ヒープ
┌─────────────┐
│ s           │            ┌───────────┐
│ ptr ─────────────────────→│ "hello"   │
│ len: 5      │            └───────────┘
│ cap: 5      │                  ↑
└─────────────┘                  │
       ↑                         │
┌──────┴──────┐                  │
│ r (&s)      │                  │
│ ptr ────────┘ (sを指す)
└─────────────┘

rs参照しています。sの所有権は移動しません。

借用(Borrowing)

参照を作ることを借用(Borrowing)と呼びます。

本を図書館から借りるイメージです:

  • 図書館(所有者)は本を持っている
  • あなた(借用者)は本を読める
  • 読み終わったら返す(参照のスコープが終わる)
  • 本は図書館のまま(所有権は移動しない)

不変参照(Immutable Reference)

&で作る参照は読み取り専用です。

fn main() {
    let s = String::from("hello");
    let r1 = &s;
    let r2 = &s;  // 複数の不変参照OK

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

不変参照のルール

  • 複数の不変参照を同時に持てる
  • 参照先のデータを変更できない
fn main() {
    let s = String::from("hello");
    let r = &s;

    // r.push_str(" world");  // エラー!不変参照からは変更できない
}

可変参照(Mutable Reference)

&mutで作る参照は変更可能です。

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s);  // "hello, world"
}

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

可変参照のルール(重要!)

ルール: 可変参照は同時に1つだけ

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

    let r1 = &mut s;
    // let r2 = &mut s;  // エラー!可変参照は1つだけ

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

なぜ1つだけ?

データ競合を防ぐためです。

データ競合は以下の3つが同時に起きると発生します:

  1. 2つ以上のポインタが同じデータにアクセス
  2. 少なくとも1つが書き込みを行う
  3. 同期機構がない

Rustは「可変参照は1つだけ」というルールで、コンパイル時にこれを防ぎます。

不変参照と可変参照の混在

不変参照がある間は、可変参照を作れません

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

    let r1 = &s;      // OK
    let r2 = &s;      // OK
    // let r3 = &mut s;  // エラー!不変参照があるので可変参照は作れない

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

ただし、不変参照が使われなくなった後なら可変参照を作れます:

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);
}

参照のルールまとめ

┌────────────────────────────────────────────────────┐
│               参照のルール                          │
├────────────────────────────────────────────────────┤
│ 1. 不変参照(&T)は複数同時に存在できる              │
│ 2. 可変参照(&mut T)は同時に1つだけ                │
│ 3. 不変参照と可変参照は同時に存在できない            │
│ 4. 参照は常に有効でなければならない                  │
└────────────────────────────────────────────────────┘

ダングリング参照

ダングリング参照とは、解放されたメモリを指す参照です。

fn main() {
    let r = dangle();
}

fn dangle() -> &String {  // コンパイルエラー!
    let s = String::from("hello");
    &s  // sへの参照を返そうとしている
}  // ← sがドロップされる。参照先がなくなる!

Rustはこれをコンパイル時に防ぎます

解決策: 所有権を返す

#![allow(unused)]
fn main() {
fn no_dangle() -> String {
    let s = String::from("hello");
    s  // 所有権を返す(参照ではなく値自体)
}
}

実践例

例1: 最長の文字列を返す

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

    let result = longest(&s1, &s2);
    println!("最長: {}", result);
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

'aはライフタイムで、次のドキュメントで学びます)

例2: Vecの要素を変更

fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];

    for n in &mut numbers {
        *n *= 2;  // 各要素を2倍に
    }

    println!("{:?}", numbers);  // [2, 4, 6, 8, 10]
}

まとめ

種類構文特徴
不変参照&T読み取り専用、複数OK
可変参照&mut T変更可能、1つだけ
ルール内容
ルール1不変参照は複数OK
ルール2可変参照は1つだけ
ルール3不変と可変は同時に存在不可
ルール4参照は常に有効(ダングリング禁止)

確認テスト

Q1. 不変参照(&T)について正しいものはどれですか?

Q2. 可変参照(&mut T)について正しいものはどれですか?

Q3. 以下のコードはコンパイルできますか?
let mut s = String::from("hello"); let r1 = &s; let r2 = &mut s; println!("{}, {}", r1, r2);

Q4. 関数内で文字列を変更したい場合、引数の型として正しいものはどれですか?

Q5. 「借用」(Borrowing)の概念について正しい説明はどれですか?


次のドキュメント: 04_lifetimes_basic.md