参照の話

    Loading...

## 参照を理解する

参照とは、ある値へのポインタを保持することである。 そのポインタを利用して、値を書き換えたり読み取ったりできる。

Rustでは参照周りのルールが複雑だが、それは危ない操作を防ぐために必要なものである。

## 参照のルールのお気持ち

### データ競合可能性の排除

データ競合が発生しうるコードのコンパイルを防ぐため、参照にはいくつかの制約がある。

具体的には以下の3つの条件を同時に満たす場合にデータ競合が発生しうる。

  • 2つ以上のポインタが同じデータに同時にアクセスする。
  • 少なくとも1つのポインタがデータに書き込みを行っている。
  • データへのアクセスを動悸する機構が使用されていない。

### ダウンリングポインタの排除

ダウンリングポインタとは、他人に渡されてしまった可能性のあるメモリを指すポインタのことである。 その箇所へのポインタを保持している間に、メモリを開放してしまうことで発生する。

Rustでは、ダウンリングポインタが発生しないように設計されているため、ポインタが指す値が必ず存在することが保証される。

## 参照のルール

実装でお気持ちを実際に確認してみる

### 複数の可変参照の禁止

同じスコープで同時に2つの可変参照を持つことはできない。

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

let r1 = &mut s;
let r2 = &mut s; // E0499

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

:::note[お気持ち] r1r2が同じデータを指しているため、どちらかがデータを書き換えた場合にもう一方の参照が不正になる可能性がある。 :::

### 可変参照と普遍参照が同時に存在することの禁止

同じスコープで可変参照と普遍参照を同時に持つことはできない。

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

let r1 = &s;
let r2 = &s; // 不変参照は同時に存在してOK
let r3 = &mut s; // E0502

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

:::note[お気持ち] 不変参照をしているr2 (r1はすでに所有権を失っている) から見ると、sが書き換わることを想定していない。 裏でs3がこっそりsを書き換えられる可能性があり、不変参照の直感に反するので禁止。 :::

関数を使った例

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

  let word = first_word(&s);

  s.clear(); // E0502

  println!("The first word is {}", word);
}

first_word()関数はsを不変で借用している。 そのあとのs.clear()では、sを可変参照しようとしているため、エラーになる。

clear()の実装:

pub fn clear(&mut self) {
    self.vec.clear()
}

### 宙に浮いた参照の排除

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

// E0106
fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

E0106のエラーメッセージは、ライフタイムに関するものであり、まだ知らない機能である。

:::note[お気持ち] dangle()の返り値は&sであり、sdangle()内部で作られた変数である。 つまりdangle()が終了した時点でsはスコープを抜けて解放される。

返り値&smain()で扱えることになってしまい、その後もsを指し続けることになる。 その結果として、main()関数内で存在しないメモリを指す参照を扱うことになり、危険である。 :::

#### 解決法

Stringを直接返すことで解決する:

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

所有権がMoveされるので、関数を抜けたあとでも問題なく変数s(から所有権が移ったもの)を扱える。