2フェーズ借用
2フェーズ借用は、vec.push(vec.len()) のようなネストしたメソッド呼び出しを可能にする、可変借用のより寛容なバージョンです。このような借用は、最初は「予約」フェーズで共有借用として振る舞い、後で完全な可変借用へ「有効化」できます。
特定の暗黙的な可変借用だけが 2フェーズになり得ます。ソースコード内のどの &mut や ref mut も 2フェーズ借用になることはありません。2フェーズ借用を生成するケースは次のとおりです。
- 可変参照レシーバーを持つメソッドを呼び出す際の autoref 借用。
- 関数引数内の可変再借用。
- オーバーロードされた複合代入演算子における暗黙的な可変借用。
いくつか例を示します。
#![allow(unused)]
fn main() {
// ソースコード内
// ケース 1:
let mut v = Vec::new();
v.push(v.len());
let r = &mut Vec::new();
r.push(r.len());
// ケース 2:
std::mem::replace(r, vec![1, r.len()]);
// ケース 3:
let mut x = std::num::Wrapping(2);
x += x;
}
これらを 2フェーズ借用が分かる程度に展開すると、次のようになります。
// ケース 1:
let mut v = Vec::new();
let temp1 = &two_phase v;
let temp2 = v.len();
Vec::push(temp1, temp2);
let r = &mut Vec::new();
let temp3 = &two_phase *r;
let temp4 = r.len();
Vec::push(temp3, temp4);
// ケース 2:
let temp5 = &two_phase *r;
let temp6 = vec![1, r.len()];
std::mem::replace(temp5, temp6);
// ケース 3:
let mut x = std::num::Wrapping(2);
let temp7 = &two_phase x;
let temp8 = x;
std::ops::AddAssign::add_assign(temp7, temp8);
借用が 2フェーズになり得るかどうかは、型検査後に AutoBorrow 上のフラグで追跡され、その後 MIR 構築中に BorrowKind へ 変換 されます。
各 2フェーズ借用は、一度だけ使用される一時変数に割り当てられます。そのため、次のように定義できます。
- 一時変数が代入されるポイントを、その 2フェーズ借用の予約ポイントと呼びます。
- 一時変数が使用されるポイントは、実質的に常に関数呼び出しであり、有効化ポイントと呼びます。
有効化ポイントは GatherBorrows visitor を使用して見つけられます。その後、BorrowData はその借用の予約ポイントと有効化ポイントの両方を保持します。
2フェーズ借用の検査
2フェーズ借用は、次の例外を除き、可変借用であるかのように扱われます。
- MIR 内のすべての location で、この location において有効化される 2フェーズ借用があるかどうかを 検査 します。live な 2フェーズ借用がある location で有効化される場合、その 2フェーズ借用と競合する借用がないことを検査します。
- 予約ポイントでは、競合する live な可変借用があればエラーにします。また、競合する共有借用があれば lint します。
- 予約ポイントと有効化ポイントの間では、2フェーズ借用は共有借用として振る舞います。そのようなポイントにいるかどうかは、MIR グラフの
Dominatorsを使用して(is_active内で)判定します。 - 有効化ポイントの後では、2フェーズ借用は可変借用として振る舞います。