借用チェッカーの の制限と改善の歴史。

歴史

初期

参照のライフタイムを字句スコープ ('{' から '}' まで) に対応させて管理する方式。

この方式は単純で理解しやすい。しかし、スコープが終了する位置と参照が不要になる位置は一般には一致しない (スコープの最後のコードが参照へのアクセスでもない限り、参照はより早くお役御免となる)。そのため、ライフタイムが無駄に延長され偽陽性につながりやすい。そして、これを避けるには冗長なスコープがそのつど必要になる。

現在

NLL (Non-lexical lifetimes)

参照のライフタイムをそれが必要なコード位置の集合として管理する方式。

前のバージョンと比べると、不要になった参照からの制限がより素早く解除される。ただし、条件分岐があるとライフタイムは各分岐の要求の和で作られる。これは条件分岐内で参照が不要となるパターンにおいて、直感と異なるチェック基準となりやすい。

偽陽性の例 1

以下では、最後の r1 の使用が r2 の存在のためエラーになる。なお、この位置では本来 r2 はすでに不要なはずだが、条件分岐内の要求などから、r2 のライフタイムは以下のように判断されてしまっている。

  • r1r2 の起源のため [r1 のライフタイム] ⊇ [r2 のライフタイム] が必要
  • r1r2 を代入するため [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

偽陽性の例 2

以下では、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 を試したい場合、以下が必要になる。

  • ナイトリー版を有効化
    (具体的な方法は『バージョンの切替』を参照)

  • ビルド時のコマンドライン引数で Polonius を有効化。
    (以下は .cargo/config.toml ファイルからの引数の指定方法)

    
    [build]
    rustflags = ["-Zpolonius"]