型としての参照
Essential前提知識
プログラムの2つの部分が同じデータを扱う必要があるとき、毎回コピーしたくはない。**参照(reference)**を使えば、値そのものの代わりに既存の値へのハンドルを渡せる — 安全に、生ポインタの危険なしに。
&T — 共有参照
共有参照(shared reference) &T は、どこかにある T への読み取り専用アクセスを提供する。同じ値への &T 参照は、同時にいくつでも持てる。
let x: i32 = 42;
let r: &i32 = &x; // r は x を指す
r 自体は x のアドレスを値として持つ変数だ。整数を所有しているのではなく、単にその view(ビュー)を借用している。
&mut T — 排他的(ミュータブル)参照
ミュータブル参照(mutable reference) &mut T は読み書きアクセスを提供する。その代わり、特定の値への &mut T は任意の時点でただ1つしか持てない — 同時に同じ値への他の参照は存在できない。
let mut y: i32 = 10;
let m: &mut i32 = &mut y;
*m += 1; // y は 11 になる
この排他性ルールによってRustはデータ競合がなく、エイリアスされた書き込みもないことを保証する。コンパイラがこれを強制する;詳細は借用チェッカーの章で学ぶ。
参照の作成
参照は &(共有)または &mut(ミュータブル)をプレイスを生成する任意の式に適用して作る:
let values = [1, 2, 3];
let ref_to_array: &[i32; 3] = &values;
let mut count = 0_u32;
let ref_to_count: &mut u32 = &mut count;
&expr と &mut expr が参照を作る唯一の方法だ。別の「アドレス演算子」はない — & だけだ。
参照を通じた読み取り
参照が指す値を取得するには、* で**デリファレンス(dereference)**する:
let n: i32 = 7;
let r: &i32 = &n;
let v = *r; // v == 7、参照先からコピーされる
実際には、Rustは多くの場面で**自動デリファレンスコアーション(automatic deref coercions)**を挿入するため、* を手で書く必要はほとんどない。メソッド呼び出しとフィールドアクセスは自動的に参照を辿る:
let s = String::from("hello");
let r: &String = &s;
let length = r.len(); // 明示的な * 不要;Rustが挿入する
明示的な * が必要な場合 — たとえばミュータブル参照を通じて代入するとき — 書く:
let mut x = 0_i32;
let m = &mut x;
*m = 99; // 参照を通じて代入する
参照のサイズ
T が何であれ、&T と &mut T はどちらもちょうど usize 1つ分の幅だ — マシンポインタ1個分。参照先は u8 でも1 MBの構造体でも構わない;参照は常にポインタサイズだ。
use std::mem::size_of;
assert_eq!(size_of::<&u8>(), size_of::<usize>());
assert_eq!(size_of::<&[u8; 1024]>(), size_of::<usize>());
参照と生ポインタ
Rustには**生ポインタ(raw pointers)**もある:*const T(読み取り専用)と *mut T(読み書き)。見た目は似ているが動作は大きく異なる:
&T / &mut T | *const T / *mut T | |
|---|---|---|
| 常に非null | はい | いいえ |
| 有効性保証 | はい(コンパイラが検査) | いいえ |
デリファレンスに unsafe が必要 | いいえ | はい |
| 借用チェッカーで追跡 | はい | いいえ |
生ポインタはFFIや低レベルのコードのために存在する。通常のRustでは参照を使い、ルールの外に出る必要があるときだけ生ポインタに手を伸ばす。
借用ルール — 意図的に後回し
参照は**借用チェッカー(borrow checker)**の対象だ:参照が参照先より長く生きないこと、そして同じ場所への & と &mut が同時に生きていないことをコンパイル時に証明するルールの集合だ。それらのルール — ライフタイム(lifetime)、借用スコープ、実際に見るエラーメッセージ — は専用のチェックポイントでカバーする。今は参照を型付きアドレスとして純粋に扱う。
Zigとのアナロジー
Zigを知っているなら、対応は直接的だ:
- Rustの
&T≈ Zigの*const T— 読み取り専用のポインタ - Rustの
&mut T≈ Zigの*T(非constポインタ) — 書き込み可能なポインタ
重要な違いは、Rustの型システムが参照がいつ生きていて排他的かをコンパイル時に追跡することだ;Zigはその責任をあなたに任せる。
まとめ
&Tは共有(読み取り専用)参照で、いくつでも共存できる。&mut Tは排他的(読み書き)参照で、同時に1つしか存在できない。&expr/&mut exprで作成する。*rでデリファレンスする;Rustはメソッド呼び出しや多くの場面で自動デリファレンスする。Tに関わらずどちらもポインタサイズ(usize)だ。- 参照は常に有効だ —
unsafeなしにはデリファレンスできない生ポインタ*const T/*mut Tとは異なる。 - 参照のライフタイムとエイリアシングを管理する借用チェッカーのルールは別途カバーする。