借用チェッカーの の制限と改善の歴史。
偽陰性 (メモリ安全でないコードがコンパイルできるパターン) ⇒ 昔から皆無
偽陽性 (メモリ安全なコードなのにコンパイルできないパターン) ⇒ 徐々に改善中
参照のライフタイムを字句スコープ ('{
' から '}
' まで) に対応させて管理する方式。
この方式は単純で理解しやすい。しかし、スコープが終了する位置と参照が不要になる位置は一般には一致しない (スコープの最後のコードが参照へのアクセスでもない限り、参照はより早くお役御免となる)。そのため、ライフタイムが無駄に延長され偽陽性につながりやすい。そして、これを避けるには冗長なスコープがそのつど必要になる。
参照のライフタイムをそれが必要なコード位置の集合として管理する方式。
前のバージョンと比べると、不要になった参照からの制限がより素早く解除される。ただし、条件分岐があるとライフタイムは各分岐の要求の和で作られる。これは条件分岐内で参照が不要となるパターンにおいて、直感と異なるチェック基準となりやすい。
以下では、最後の r1
の使用が r2
の存在のためエラーになる。なお、この位置では本来 r2
はすでに不要なはずだが、条件分岐内の要求などから、r2
のライフタイムは以下のように判断されてしまっている。
r1
と r2
の起源のため [r1
のライフタイム] ⊇ [r2
のライフタイム] が必要
r1
に r2
を代入するため [r1
のライフタイム] ⊆ [r2
のライフタイム] が必要
r1
のライフタイム] = [r2
のライフタイム] が必要
fn main() {
let mut x = 42;
let mut r1 = &mut x;
let r2 = &mut *r1;
if condition() {
r1 = r2;
}
stuff(*r1);
}
fn condition() -> bool {
true
}
fn stuff<T>(_x: T) {
// nop.
}
error[E0503]: cannot use `*r1` because it was mutably borrowed --> src/main.rs:10:11 | 4 | let r2 = &mut *r1; | -------- `*r1` is borrowed here ... 10 | stuff(*r1); | ^^^ | | | use of borrowed `*r1` | borrow later used here
以下では、map
の編集が value
の存在のためエラーになる。なお、この位置では value
はすでに不要のはずだが、最初の分岐アームの要求から、map
のライフタイムの借用対象となってしまっている。
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
let val = or_default(&mut map, 42);
assert_eq!(*val, 0);
}
fn or_default(map: &mut HashMap<i32, i32>, key: i32) -> &mut i32 {
match map.get_mut(&key) {
Some(value) => value,
None => {
map.insert(key, i32::default());
map.get_mut(&key).unwrap()
}
}
}
error[E0499]: cannot borrow `*map` as mutable more than once at a time --> src/main.rs:13:13 | 9 | fn or_default(map: &mut HashMap<i32, i32>, key: i32) -> &mut i32 { | - let's call the lifetime of this reference `'1` 10 | match map.get_mut(&key) { | - --- first mutable borrow occurs here | _____| | | 11 | | Some(value) => value, 12 | | None => { 13 | | map.insert(key, i32::default()); | | ^^^ second mutable borrow occurs here 14 | | map.get_mut(&key).unwrap() 15 | | } 16 | | } | |_____- returning this value requires that `*map` is borrowed for `'1` error[E0499]: cannot borrow `*map` as mutable more than once at a time --> src/main.rs:14:13 | 9 | fn or_default(map: &mut HashMap<i32, i32>, key: i32) -> &mut i32 { | - let's call the lifetime of this reference `'1` 10 | match map.get_mut(&key) { | - --- first mutable borrow occurs here | _____| | | 11 | | Some(value) => value, 12 | | None => { 13 | | map.insert(key, i32::default()); 14 | | map.get_mut(&key).unwrap() | | ^^^ second mutable borrow occurs here 15 | | } 16 | | } | |_____- returning this value requires that `*map` is borrowed for `'1`
参照と借用を個別に追跡する方式。
前のバージョンにあった不自然な偽陽性がなくなっている。
開発中の Polonius を試したい場合、以下が必要になる。
ナイトリー版を有効化
(具体的な方法は『バージョンの切替』を参照)
ビルド時のコマンドライン引数で Polonius を有効化。
(以下は .cargo/config.toml
ファイルからの引数の指定方法)
[build]
rustflags = ["-Zpolonius"]