Rustのエラー E0499: cannot borrow as mutable more than once at a time(可変借用が2つ)
エラー E0499 は、同じ値への可変参照(&mut)を同時に2つ持とうとした、という意味です。借用チェッカーのエラーの中でもとくによく出るものの1つですが、直し方はたいてい小さく、1行動かすだけで済むこともよくあります。(ほかのエラーを探しているなら、コンパイルエラーの一覧を見てください。)
エラーメッセージ
Section titled “エラーメッセージ”error[E0499]: cannot borrow `balance` as mutable more than once at a time --> src/main.rs:5:20 |4 | let deposit = &mut balance; | ------------ first mutable borrow occurs here5 | let withdraw = &mut balance; | ^^^^^^^^^^^^ second mutable borrow occurs here6 |7 | *deposit += 50; | -------------- first borrow later used here1行目は「balance を一度に2回以上、可変で借用することはできない」という意味です。ラベルは順に「1つ目の可変借用がここで起きる」「2つ目の可変借用がここで起きる」「1つ目の借用があとでここで使われる」と言っています。
このエラーが出るのは、次のプログラムです。
fn main() { let mut balance = 100;
let deposit = &mut balance; let withdraw = &mut balance;
*deposit += 50; *withdraw -= 30;
println!("Balance: {balance}");}エラーの意味
Section titled “エラーの意味”コンパイラは3か所を指していて、合わせて読むと何が起きたのか全部わかります。
- 4行目:
depositがbalanceの可変借用になります。 - 5行目:
withdrawが、balanceの2つ目の可変借用になろうとします。 - 7行目: 5行目のあとで、まだ
depositが使われています。つまり、2つ目の借用が始まった時点で、1つ目の借用はまだ生きています。
大事なのは3つ目のラベルです。2つの &mut 借用が問題になるのは、その期間が重なっているときだけです。deposit を5行目より前に使い終えていれば、エラーにはなりません。
「編集」の鍵が1本だけある共有ドキュメントを思い浮かべてください。鍵を持っている人だけがドキュメントを書き換えられます。Rust は鍵の合鍵を作ってくれないので、2人目は1人目が鍵を返すまで待つしかありません。
Rust がこうする理由
Section titled “Rust がこうする理由”Rust の借用のルールは、「どの瞬間にも、読む人(&)はたくさん、または書く人(&mut)は1人だけ」です。書く人が2人以上になることはありません。&mut 参照は「自分がいる間は、ほかの誰もこの値に触れない」という約束なのです。
この約束が、本物のバグを防いでいます。もし2つのコードが同じ値を同時に書き換えられたら、片方が Vec のサイズを変えている間に、もう片方がまだ古いメモリを指している、ということが起こりえます。ほかの言語では、それはクラッシュや、気づかないうちのデータ破壊になります。スレッドをまたげば、データ競合(data race)です。
このルールのおかげで、コンパイラは「&mut が生きている間、データにたどり着ける道はそれしかない」と確信できます。しかもこの安全性は、実行時のコストがゼロです。その代わり、「この2つの借用は本当はぶつからない」ということを、コンパイラにもっとはっきり伝えなければならないこともあります。
直し方1:1つ目の借用を終えてから2つ目を始める
Section titled “直し方1:1つ目の借用を終えてから2つ目を始める”まずはこれを選びましょう。 E0499 のほとんどはこれで直ります。借用が続くのはブロックの終わりまでではなく、最後に使われた場所までです。なので、1つ目の参照での作業を全部済ませてから、2つ目を作ります。
fn main() { let mut balance = 100;
let deposit = &mut balance; *deposit += 50; // deposit を使うのはここが最後なので、借用はここで終わる
let withdraw = &mut balance; // ✅ 今はほかに誰も借用していない *withdraw -= 30;
println!("Balance: {balance}");}実行結果:
Balance: 120(エラーになる例と比べやすいように、ここでは表示する文字列を英語のままにしています。)
実は、余分な変数がまったくいらないこともよくあります。balance += 50; のあとに balance -= 30; と書いても、同じように動きます。
直し方2:重ならない部分を別々に借用する
Section titled “直し方2:重ならない部分を別々に借用する”2つの &mut が同時に本当に必要で、でもそれぞれがデータの別の部分を指している、ということもあります。コンパイラにはそれが見抜けないことがあるので、こちらから示してあげます。
Vec やスライスの2つの要素の場合、tasks[0] も tasks[1] も、tasks 全体を借用してしまいます。split_at_mut(英語)を使えば、重ならないことが保証された2つの半分に切り分けられます。
fn main() { let mut tasks = vec![String::from("書く"), String::from("テストする")];
// Vec を、重ならない2つの半分に分ける let (left, right) = tasks.split_at_mut(1); let first = &mut left[0]; let second = &mut right[0];
std::mem::swap(first, second); println!("{tasks:?}");}実行結果:
["テストする", "書く"](入れ替えるだけなら、tasks.swap(0, 1) のほうがもっと簡単です。split_at_mut は、両方の参照を使って本当に作業したいときのためのものです。)
構造体の場合も、p.name_mut() と p.score_mut() のように、どちらも &mut self を受け取るメソッドを2つ呼ぶと、同じエラーが出ます。メソッドを呼ぶたびに、構造体全体が借用されるからです。代わりにフィールドを直接借用すれば、コンパイラにも別々のものだとわかります。
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), score: 0 };
// メソッドを通さず、フィールドを直接借用する let name = &mut p.name; let score = &mut p.score;
name.push_str("(カニ)"); *score += 10; println!("{}は{}点", p.name, p.score);}実行結果:
Ferris(カニ)は10点直し方3:参照を持ち続けずにインデックスを使う
Section titled “直し方3:参照を持ち続けずにインデックスを使う”よくあるのが、iter_mut() で Vec をループしながら、ループの中でその Vec に push しようとするパターンです。イテレータはループの間ずっと &mut を持っているので、push が2つ目の &mut になってしまいます。
fn main() { let mut queue = vec![1, 2, 3]; for job in queue.iter_mut() { *job *= 10; queue.push(*job); // ❌ E0499: queue はすでに iter_mut() に借用されている }}ループの中でコレクションそのものを書き換える必要があるなら、代わりに位置(インデックス)でループします。インデックスはただの数値なので、何も借用しません。
fn main() { let mut queue = vec![1, 2, 3];
// queue の中を指す参照を持たずに、位置でループする for i in 0..queue.len() { queue[i] *= 10; let job = queue[i]; queue.push(job); }
println!("{queue:?}");}実行結果:
[10, 20, 30, 10, 20, 30]0..queue.len() はループが始まる前に1回だけ計算されるので、新しく追加した要素をもう一度回ることはありません。queue[i] の借用は、それぞれ1行の間しか続きません。
直し方4:RefCell(最後の手段)
Section titled “直し方4:RefCell(最後の手段)”RefCell を使うと、借用チェックがコンパイル時から実行時に移ります。可変借用が同じ瞬間に2つ生きていない限り、mut の付いていない変数でも borrow_mut() を呼べます。
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100); // `mut` はいらない
*balance.borrow_mut() += 50; // 借りて、書き換えて、`;` で借用が終わる *balance.borrow_mut() -= 30;
println!("残高: {}", balance.borrow());}実行結果:
残高: 120ここは正直に言っておきます。RefCell はルールをなくすわけではなく、チェックをあとに回すだけです。borrow_mut() のガードを同時に2つ生かしておくと、プログラムはコンパイルできても、実行したときにパニックします。
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100);
let mut deposit = balance.borrow_mut(); let mut withdraw = balance.borrow_mut(); // 💥 deposit がまだ生きている
*deposit += 50; *withdraw -= 30;}thread 'main' (12345) panicked at src/main.rs:7:32:RefCell already borrowedこれは「RefCell はすでに借用されている」という、実行時のパニックメッセージです。
なので、RefCell を使うのは、データが本当に共有されていて(たとえば Rc<RefCell<T>> の中など)、直し方1〜3が合わないときだけにしましょう。コンパイラを黙らせるための道具ではありません。
よく出会う場面
Section titled “よく出会う場面”- ループの中で
&mutをしまっておくとき。forループの中でhandles.push(&mut counter)と書くと、「mutably borrowed here in the previous iteration of the loop」(前回のループでここで可変借用された)というエラーになります。ループが1周するたびに、同じ値への&mutが1つずつ増えていくからです。直し方: インデックスや所有した値をしまうか、ループの中で値を直接書き換えます。 HashMapでget_mutを2回呼ぶとき。let a = stock.get_mut("apples")のあとにlet p = stock.get_mut("pears")と書くと、stockを2回借用することになります。直し方: もう一度get_mutを呼ぶ前にaを使い終えるか、stock.get_disjoint_mut(["apples", "pears"])で両方を一度に取り出します。- 同じ変数を書き換えるクロージャが2つあるとき。
let mut inc = || count += 1;とlet mut inc2 = || count += 2;は、どちらも生きている間ずっと&mut countを持っています。直し方: クロージャを1つにまとめるか、2つ目を作る前に1つ目を呼び終えます。詳しくはクロージャを見てください。
関連するエラー
Section titled “関連するエラー”- E0502: cannot borrow as mutable because it is also borrowed as immutable:同じ考え方のエラーですが、片方の借用が読み取り専用の
&です。 - E0382: borrow of moved value:値をほかの場所にムーブしたあとで使った。
- E0106: missing lifetime specifier:返す参照がいつまで生きるのか、Rust が知る必要がある。
- コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. Rust が E0499 を出すのはどんなときですか?
2. let a = &mut v; を作り、a を最後に使ったあとで let b = &mut v; を作りました。どうなりますか?
3. 同じスライスの別々の要素への &mut 参照を2つ手に入れられるのはどれですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0499 — Rust error codes index(英語):このエラーの公式の説明です。最小限の例も載っています。
- 参照と借用 — The Rust Programming Language 日本語版:「
&mutは一度に1つだけ」というルールを紹介している章です。 - Splitting Borrows — The Rustonomicon(英語):構造体のフィールドは別々に借用できるのに、スライスの要素はできない理由と、
split_at_mutのしくみです。 RefCell<T>と内部可変性パターン — The Rust Programming Language 日本語版:借用チェックを実行時に回すのが正しい選択になるのはどんなときかを解説しています。