エラーの内容
同じ変数への2つのミュータブル参照が同時に生存しています。Rustコンパイラは即座にこれを検出し、ビルドを拒否します:
error[E0499]: cannot borrow `vec` as mutable more than once at a time
--> src/main.rs:5:18
|
4 | let first = &mut vec;
| -------- first mutable borrow occurs here
5 | let second = &mut vec;
| ^^^^^^^^ second mutable borrow occurs here
6 | println!("{}", first[0]);
| ----- first borrow later used here
これはボローチェッカーが正常に機能している結果です。ルールは厳格です:一度に1つのミュータブル参照のみ、例外はありません。
なぜこのエラーが発生するのか
Rustはコンパイル時にデータ競合を防ぎます。同じデータへの2つの同時ミュータブル参照があると、一方がデータを変更または無効化している間に、もう一方が読み取る可能性があります。これはC/C++における典型的なバグですが、Rustはこれを構造的に不可能にしています。
難しいのは、「同時に」という表現が直感と異なる点です。ミュータブル借用は、次の変数を代入した時点ではなく、その最後の使用箇所まで生存し続けます。他の言語から来た開発者の多くがここで躓きます。
ステップごとの修正方法
ステップ1:重複している借用を特定する
rustc --explain E0499 を実行して、コンパイラの詳細な説明を確認しましょう。エラー出力には、各借用の開始位置と使用中の箇所が正確に示されます。目標は、最初のミュータブル借用が完全に終了してから2番目の借用を開始することです。
ステップ2:最初の借用をスコープ外に出す
最もシンプルな修正方法は、最初の参照を完全に使い終えてから2番目の参照を作成することです。
fn main() {
let mut vec = vec![1, 2, 3];
// BAD: both borrows alive at the same time
// let first = &mut vec;
// let second = &mut vec; // error!
// GOOD: first borrow is done before second begins
{
let first = &mut vec;
first.push(4);
} // ここで最初の借用が終了する
let second = &mut vec;
second.push(5);
println!("{:?}", vec); // [1, 2, 3, 4, 5]
}
ステップ3:コレクションには複数参照の代わりにインデックスを使う
同じVecの2つの要素を同時にミュータブル参照しようとすることは、E0499が発生する最も頻繁な原因の一つです。インデックスによるアクセスはこの問題を完全に回避できます:
fn main() {
let mut data = vec![10, 20, 30];
// BAD: two mutable references into the same Vec
// let a = &mut data[0];
// let b = &mut data[1]; // error!
// GOOD: use indices directly
data[0] += data[1]; // 1つの文で[1]を読み取り[0]に書き込む
println!("{:?}", data); // [30, 20, 30]
// Or: split the slice
let (left, right) = data.split_at_mut(1);
left[0] = right[0] + right[1];
println!("{:?}", data); // [50, 20, 30]
}
ステップ4:分離したスライスのミュータブル操作にはsplit_at_mutを使う
同じコレクションの異なる部分への2つのミュータブルビューが必要ですか?split_at_mutはスライスを重ならない2つのミュータブルな半分に分割します。2つの部分がエイリアスになり得ないことが証明できるため、ボローチェッカーはこれを受け入れます:
fn swap_first_last(data: &mut Vec<i32>) {
let len = data.len();
if len < 2 {
return;
}
let (head, tail) = data.split_at_mut(len - 1);
std::mem::swap(&mut head[0], &mut tail[0]);
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
swap_first_last(&mut v);
println!("{:?}", v); // [5, 2, 3, 4, 1]
}
ステップ5:ロジックを別々のパスに分割する
設計自体が問題になっている場合もあります。同じ構造体への読み取りと書き込みを同時に行うことは本質的に競合を引き起こします。代わりに2つの独立したパスに分割しましょう:
fn main() {
let mut scores: Vec<i32> = vec![3, 7, 2, 9, 1];
// BAD pattern: trying to find max and update in one pass
// with multiple mutable borrows
// GOOD: two separate passes
let max = *scores.iter().max().unwrap(); // 読み取りパス(イミュータブル借用)
for s in scores.iter_mut() { // 書き込みパス(ミュータブル借用)
*s = if *s == max { 100 } else { *s };
}
println!("{:?}", scores); // [3, 7, 2, 100, 1]
}
ステップ6:必要な場合は内部ミュータビリティにRefCellを使う
ボローチェッカーが過度に保守的になることがあります。グラフ構造や特定の再帰的なパターンが典型的な例です。RefCell<T>は脱出口となります:コンパイル時ではなくランタイムに借用チェックを延期します。
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
{
let mut borrow = data.borrow_mut();
borrow.push(4);
} // ここでミュータブル借用が解放される
println!("{:?}", data.borrow()); // [1, 2, 3, 4]
}
警告:RefCellは実際に借用ルールを違反するとランタイムでパニックします。最後の手段として扱いましょう。コードの再設計がほぼ常により良い解決策です。
修正の確認
プロジェクトをビルドしてE0499が解消されたことを確認します:
cargo build
次に、テストを実行して何も壊れていないことを確認します:
cargo test
cargo clippy も実行する価値があります。コンパイラに到達する前に、このエラーを引き起こしやすいパターンを検出してくれます:
cargo clippy
クイックリファレンス:どの修正を使うべきか
- 実際には借用が重複していない場合? ブロック
{ }を追加して最初の借用のスコープを限定する。 - Vecの2つの要素を操作する場合?
split_at_mut()またはインデックス演算を使う。 - 読み取りから書き込みのパターン? 2つのパスに分割する。
- 複雑な共有状態?
RefCell<T>(シングルスレッド)またはMutex<T>(マルチスレッド)を使う。 - グラフ・ツリー構造?
slotmapやgenerational-arenaのようなアリーナクレートを検討する。
よくある間違い
- 借用が代入行で終わると思い込む。 そうではありません。借用はその最後の使用箇所まで生存します。Rust 2018で導入されたNLL(Non-Lexical Lifetimes)は多くの明白なケースを自動的に処理しますが、複雑なパターンは依然として問題を引き起こすことがあります。
- 最初から
RefCellに頼る。 これは便利ツールではなく最後の手段です。コードの再設計がほぼ常に正しい選択です。 - フィールドへの参照を保持しながら
&mut selfメソッドを呼び出す。 そのメソッド呼び出しは構造体全体をミュータブルに借用します。そのメソッドが特定のフィールドに触れなくても、構造体のどのフィールドへの既存の参照とも競合します。

