already borrowed: BorrowMutError パニックの原因と直し方
RefCellは借用規則を実行時にチェックする仕組みです。borrow_mut()で可変借用した値がまだ生きている間に、もう一度borrow_mut()やborrow()を呼ぶとパニックします。
エラーメッセージの読み方
thread 'main' panicked at main.rs:6:19:
main.rs- ファイル名
6- 行番号 — 実際にクラッシュした行
19- 列番号
panicked- パニック — 実行時に回復不能な状態になったことを示します。tryやResultで捕まえない限り、そのままプロセスが終了します
already borrowed: BorrowMutError- 詳細メッセージ — どの値が問題だったか
このエラーが出る典型パターン
パターン1
1 use std::cell::RefCell; 2 fn main() { 3 let data = RefCell::new(5); 4 let b1 = data.borrow_mut(); 5 ^ 6 let b2 = data.borrow_mut(); 7 println!("{}", b2); 8 }
b1が生きたまま2回目のborrow_mut()を呼ぶとパニックします。dropで先に借用を終了させる必要があります。
直し方: (空) を drop(b1); にします。
パターン2
1 use std::cell::RefCell; 2 fn main() { 3 let counter = RefCell::new(0); 4 let c1 = counter.borrow_mut(); 5 ^ 6 let c2 = counter.borrow_mut(); 7 println!("{}", c2); 8 }
RefCellは借用ルールを実行時にチェックするため、違反すればコンパイルは通っても実行時にパニックします。
直し方: (空) を drop(c1); にします。
パターン3
1 use std::cell::RefCell; 2 fn main() { 3 let cache = RefCell::new(10); 4 let x1 = cache.borrow_mut(); 5 ^ 6 let x2 = cache.borrow_mut(); 7 println!("{}", x2); 8 }
1つ目の可変借用を使い終わったら、明示的にdropして解放してから次の借用を行う必要があります。
直し方: (空) を drop(x1); にします。
パターン4
1 use std::cell::RefCell; 2 fn main() { 3 let state = RefCell::new(1); 4 let s1 = state.borrow_mut(); 5 ^ 6 let s2 = state.borrow_mut(); 7 println!("{}", s2); 8 }
コンパイル時の借用チェックが効かない代わりに、RefCellは違反時に確実にパニックで知らせます。
直し方: (空) を drop(s1); にします。
パターン5
1 use std::cell::RefCell; 2 fn main() { 3 let total = RefCell::new(100); 4 let t1 = total.borrow_mut(); 5 ^ 6 let t2 = total.borrow_mut(); 7 println!("{}", t2); 8 }
すでに可変借用されている状態でさらに借用しようとすると、already borrowedというパニックになります。
直し方: (空) を drop(t1); にします。
よくある誤解
「RefCellを使えば、借用ルールを気にせず何回でも借用できるはず」という思い込みは誤りです。RefCellはコンパイル時ではなく実行時に借用ルールをチェックする仕組みで、違反すればその場でパニックします。
実務での勘所
RefCellは、コンパイル時に行われる借用チェックを実行時に肩代わりする仕組みです。通常の借用チェックは実行コストがゼロですが、木構造の親子が互いを参照し合う場合など、実際には安全なのにコンパイラが静的には安全性を証明できないパターンを拒否してしまうことがあります。RefCellを使うと、こうしたパターンをいったんコンパイルは通し、代わりに実行時に「同時に複数の可変借用が無いか」を追跡します。違反した場合はコンパイルエラーではなくパニックとして現れるという代償はありますが、静的なチェッカーだけでは表現しにくい設計を可能にする、意図的な逃げ道(内部可変性)です。