コンテンツにスキップ

Rustのエラー E0499: cannot borrow as mutable more than once at a time(可変借用が2つ)

エラー E0499 は、同じ値への可変参照(&mut)を同時に2つ持とうとした、という意味です。借用チェッカーのエラーの中でもとくによく出るものの1つですが、直し方はたいてい小さく、1行動かすだけで済むこともよくあります。(ほかのエラーを探しているなら、コンパイルエラーの一覧を見てください。)

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 here
5 | let withdraw = &mut balance;
| ^^^^^^^^^^^^ second mutable borrow occurs here
6 |
7 | *deposit += 50;
| -------------- first borrow later used here

1行目は「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}");
}

コンパイラは3か所を指していて、合わせて読むと何が起きたのか全部わかります。

  1. 4行目: deposit が balance の可変借用になります。
  2. 5行目: withdraw が、balance の2つ目の可変借用になろうとします。
  3. 7行目: 5行目のあとで、まだ deposit が使われています。つまり、2つ目の借用が始まった時点で、1つ目の借用はまだ生きています。

大事なのは3つ目のラベルです。2つの &mut 借用が問題になるのは、その期間が重なっているときだけです。deposit を5行目より前に使い終えていれば、エラーにはなりません。

「編集」の鍵が1本だけある共有ドキュメントを思い浮かべてください。鍵を持っている人だけがドキュメントを書き換えられます。Rust は鍵の合鍵を作ってくれないので、2人目は1人目が鍵を返すまで待つしかありません。

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行の間しか続きません。

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が合わないときだけにしましょう。コンパイラを黙らせるための道具ではありません。

  • ループの中で &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つ目を呼び終えます。詳しくはクロージャを見てください。

理解度チェック

1. Rust が E0499 を出すのはどんなときですか?

2. let a = &mut v; を作り、a を最後に使ったあとで let b = &mut v; を作りました。どうなりますか?

3. 同じスライスの別々の要素への &mut 参照を2つ手に入れられるのはどれですか?

スコア: 0 / 3