Rust で「cannot borrow as mutable more than once」(E0499) が解決できない
同じ値に対する可変参照は同時に 1 つしか持てない。
E0499 は 2 つ目の &mut を作ったときに出る。
借用のスコープを分ける、インデックスで扱う、split_at_mut や RefCell を使う、のいずれかで解決する。
公開:
要約
E0499 は、同じ値に対する可変参照を 2 つ同時に作ったときに出る。
Rust の借用規則では「不変参照は何個でも、可変参照は 1 つだけ」で、この 2 つは同時に成立しない。
let mut i = 0;
let x = &mut i;
let a = &mut i; // error[E0499]: cannot borrow `i` as mutable more than once at a time
*x += 1;借用が「いつまで生きているか」で判定されるため、2 つ目を作った行が悪いとは限らない。
1 つ目を最後に使う位置を動かすだけで通ることも多い。
よくある原因
- 借用が重なっている: 1 つ目の
&mutを後の行で使っていると、その時点まで借用が続く。
間に 2 つ目を作れば衝突する。 - 借りたままコレクションを変更している: 要素への
&mutを持ったままpushすると、再確保で参照が無効になりうるためコンパイラが止める。 - 同じ
Vecの 2 要素を同時に取ろうとしている:&mut v[0]と&mut v[1]は、コンパイラからは同じvへの二重借用に見える。 &mut selfメソッドの二重呼び出し: 引数の中で自分自身の可変メソッドを呼ぶと重なる。
解決策
1. 借用のスコープを分ける
let mut i = 0;
{
let x = &mut i;
*x += 1;
} // ここで x の借用が終わる
let a = &mut i;
*a += 1;明示的なブロックを使わなくても、1 つ目を使い終えた後なら 2 つ目を作れる。
借用は最後の使用位置までしか続かない。
2. 参照ではなくインデックスや値で扱う
let mut v = vec![1, 2, 3];
let first = v[0]; // i32 は Copy なので借用が残らない
v.push(first + 10);3. 2 要素同時なら split_at_mut を使う
let mut v = vec![1, 2, 3, 4];
let (left, right) = v.split_at_mut(2);
left[0] += right[0];標準ライブラリが「重ならないこと」を保証してくれるため、二重借用にならない。
4. どうしても共有が要るなら RefCell
use std::cell::RefCell;
let cell = RefCell::new(0);
*cell.borrow_mut() += 1;借用チェックがコンパイル時から実行時に移るだけなので、重なればパニックする。
設計を見直せるならまず 1 〜 3 を試す。
詳細は参照と借用の章(新しいタブで開く)にある。