項目15:借用チェッカを理解しよう
https://effective-rust.com/borrows.html
LT;DR
Rust の 参照 は 借用 されるため、永遠に持っておくことはできない
借用チェッカ は、あるアイテムに対する複数の 不変参照 もしくは単一の 可変参照 を許可するが、同時に両方が存在することは許可しない
参照の 生存期間 は ノンレキシカル生存期間 によって、内包するスコープの末尾ではなく、最後に使用された時点となる
借用チェッカの出力するエラーに対処する方法はいくつかある
ブロック { ... } によるスコープを追加して、生存期間を縮小する
名前を持つローカル変数を導入することで、生存期間をスコープの末尾にまで拡大する
一時的に複数のローカル変数を追加することで、借用チェッカが指摘している箇所を特定する
スマートポインタ を用いると借用チェッカを回避できることがあるので、相互接続したデータ構造を作る際に有用
自己参照データ構造 を Rust で扱うのはなるべく避けるか、ouroborous を使うことを検討しよう
hr.icon
Rust の 参照 は 借用 されるため、参照されるアイテムの 生存期間 よりも長く持っておくことはできない
参照とアクセス制御
アイテム内にアクセスする方法には以下の 3 つがある
1. item(所有): 生成と読み込み、更新、ドロップ、ムーブ が可能
2. &item(不変参照): 読み込みのみ可能
3. &mut item(可変参照): 読み込みと更新が可能
ムーブは、(新しい場所での)生成と(古い場所での)ドロップと考えれば、所有でのみ可能なのが分かる
上記のようなアクセス制御を持つため、可変参照内の 古い値を取り出し、新しい値で置き換えることはできない
code:rs
pub fn replace(item: &mut Option<Item>, val: Item) -> Option<Item> {
let previous = *item; // ここで許可されていないムーブが発生するため
*item = Some(val);
previous
}
しかし、このような操作は安全かつ有用なので、実現するための関数(std::mem::place)が用意されている
code:rs
pub fn replace(item: &mut Option<Item>, val: Item) -> Option<Item> {
std::mem::replace(item, Some(val)) // item の内容を Some(val) で置き換え、以前の値を返す
}
特に Option ではよく用いられるため、replace メソッドが用意されている
code:rs
pub fn replace(item: &mut Option<Item>, val: Item) -> Option<Item> {
item.replace(val) // item の中身の値を val で置き換え、以前の値を返す
}
借用ルール
借用ルールがあるため、複数の可変参照を引数に取る関数に、同じ値への参照を複数個渡すことはできない
code:rs
fn both_zero(left: &mut Item, right: &mut Item) {
left.contents = 0;
right.contents = 0;
}
let mut item = Item { contents: 0 };
both_zero(&mut item, &mut item);
同じ制限が、可変参照と不変参照を引数に取る関数にも適用される
code:rs
fn copy_contents(left: &mut Item, right: &Item) {
left.contents = right.contents
}
copy_contents(&mut item, &item);
Rust コンパイラは借用ルールによって、2 つの異なるポインタがメモリ上の同じアイテムを指しているかどうかを判断(Aliasing)する
これにより、複数の 不変参照 が指し示すメモリの内容が、可変参照 によって変更されることが無い ことが保証されるので、以下のようなメリットを持つコードを生成できる
最適化 の向上: たとえば値を レジスタ に キャッシュ しても、変更されることが無いので安心して処理できる
安全性 の向上: スレッド 間の非同期なメモリアクセスによる データ競合 が発生することがない
所有者 による操作
所有者による操作を理解するには、裏で参照を作っていると考えると分かりやすい
たとえば、アイテム所有者による変更は「短命の可変参照を作成し、それを通じて変更する」 のと等価である
したがって、他の参照がすでに存在する場合は変更できない
∵ 不変参照と可変参照が同時に存在することはできないため
code:rs
let mut item = Item { contents: 42 };
let r = &item;
item.contents = 42; // (&mut item).contents = 42 と等価
println!("reference to item is {:?}", r);
一方、不変参照を複数持つことは許されるので、読み出すことは可能
code:rs
let r = &item;
let contents = items.contents; // let contents = (&item).contents; と等価
もし r が可変参照ならば、不変参照と可変参照が同時に存在するためコンパイルエラーとなる
code:rs
let r = &mut item;
let contents = item.contents;
また、有効な参照(可変・不変問わず)がある間は、所有者は アイテム をムーブしたり、ドロップ したりすることはできない
∵ 参照の指す先が無効になるため(ダングリング)
code:rs
let r = &item;
let new_item = item;
println!("reference to item is {:?}", r);
このような場合、ノンレキシカル生存期間 を活用し、参照を使い終わったあとで ムーブ すると回避できる
code:rs
let r = &item;
println!("reference to item is {:?}", r);
let new_item = item;
借用チェッカ との戦いに勝利する
長くなったので別ページに移動 radish-miyazaki.icon
借用チェッカとの戦いに勝利する
#Rust #Effective_Rust_―_Rustコードを改善し、エコシステムを最大限に活用するための35項目