TL;DR
不変参照(&x)を保持しながら、&mut xでxを変更しようとしています。Rustは両方を同時に許可しません。可変借用が始まる前に不変借用を終了させてください。実際には、参照が重複しないようコードを再構成するか、必要なデータを事前にクローンすることを意味します。
エラーの全文
error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
--> src/main.rs:6:5
|
4 | let r = &x; // 不変借用はここで始まる
5 | println!("{}", r);
6 | x.push(4); // 可変借用がここで試みられる
| ^^^^^^^^^ 可変借用はここで発生する
7 | println!("{}", r); // 不変借用は後でここで使われる
| - 不変借用は後でここで使われる
borrowチェッカーは基本的なルールを強制します:複数の不変参照、または1つの可変参照のどちらか — 両方を同時には決して許可しません。これは恣意的な制限ではありません。ガベージコレクタなしでRustにメモリ安全性をもたらすメカニズムです。
根本原因
let r = &xと書いた瞬間、Rustはrが使用されている間、xが不変借用されているとみなします。その後x.push(4)を呼び出すと競合が発生します — コードの一部が読み取り専用ビューを保持しながら、別の部分が基礎となるデータを変更しようとするためです。C++やJavaでは、これはイテレータの無効化やデータ競合を無音で引き起こします。Rustはコンパイルを拒否します。
借用は参照の最後の使用まで続きます — ブロックの終わりまでではありません。このルールはNon-Lexical Lifetimes(NLL)と呼ばれ、Rust 2018で安定化されました。非常に役立ちますが、変更の後に不変参照を実際に使用している場合は助けにはなりません。
修正方法1 — 変更前に不変参照の使用を終了する
最も簡単な修正方法:変更が行われる前に不変借用が終了するようにコードを再構成します。
fn main() {
let mut x = vec![1, 2, 3];
// 参照を使用してからドロップする
{
let r = &x;
println!("変更前: {:?}", r);
} // <-- ここでrがスコープ外になる
x.push(4); // これで安全に変更できる
println!("変更後: {:?}", x);
}
変更の後にrを使用しなければ、余分なブロックは不要です — NLLは最後の使用時に借用を自動的に終了させます:
fn main() {
let mut x = vec![1, 2, 3];
let r = &x;
println!("変更前: {:?}", r);
// この行以降rは使用されないため、借用はここで終了する(NLL)
x.push(4);
println!("変更後: {:?}", x);
}
修正方法2 — 変更前に必要なデータをクローンする
変更後に古い値が本当に必要な場合があります。事前にクローンしましょう — 所有されたコピーにはxへの借用がありません。
fn main() {
let mut x = vec![1, 2, 3];
let snapshot = x.clone(); // 所有されたコピー、xへの借用なし
x.push(4);
println!("以前: {:?}", snapshot);
println!("現在: {:?}", x);
}
修正方法3 — 参照の代わりにインデックスを使用する
Vecを使ったコードは常にE0502に遭遇します:ある要素への参照を取得してから別の要素をpushしようとします。参照した要素には決して触れないpushでも、コンパイラはブロックします。インデックスを使えばこの問題を完全に回避できます。
// NG: vecへの参照を保存する
fn bad_example(items: &mut Vec<String>) {
let first = &items[0]; // 不変借用
items.push("new".to_string()); // 可変借用 — コンパイルエラー
println!("{}", first);
}
// OK: 参照ではなくインデックス(またはクローン)を保存する
fn good_example(items: &mut Vec<String>) {
let first = items[0].clone(); // 所有されたString、借用なし
items.push("new".to_string()); // 問題なし
println!("{}", first);
}
修正方法4 — RefCellによる内部可変性(シングルスレッド)
グラフ構造、キャッシュ、イベントシステムでは、静的なborrowチェッカーでは推論できない共有可変アクセスが必要になることがあります。RefCell<T>は、borrowチェックをコンパイル時から実行時に移すことでこれを処理します。
use std::cell::RefCell;
fn main() {
let x = RefCell::new(vec![1, 2, 3]);
let r = x.borrow(); // 不変借用(実行時)
println!("変更前: {:?}", *r);
drop(r); // 変更前に明示的に解放する
x.borrow_mut().push(4); // これで問題なし
println!("変更後: {:?}", x.borrow());
}
注意点として:2つの借用が実行時に競合した場合、RefCellは未定義動作を引き起こす代わりにパニックします。マルチスレッドコードには、代わりにArc<Mutex<T>>を使用してください。
修正方法5 — 構造体を別々のフィールドに分割する
構造体メソッド内でのE0502は通常、Rustが2つのフィールドが独立していることを認識できないことを意味します。self全体ではなく、フィールドを個別に借用してください。
struct Game {
players: Vec<String>,
log: Vec<String>,
}
impl Game {
fn record_join(&mut self, name: &str) {
// 2つの別々のフィールドを借用 — これはコンパイルできる
let players = &self.players;
let entry = format!("参加: {}, 合計: {}", name, players.len());
self.log.push(entry); // logを変更、playersではない — OK
}
}
&self全体を借用してから&mut selfを要求すると、実際のフィールドが重複していない場合でも、コンパイラには競合に見えます。self.playersとself.logに直接アクセスすることで独立性を明示できます。
修正の確認
Rustのエラーメッセージがテストスイートの代わりになります。まずここから始めてください:
cargo check
E0502が表示されなければ問題ありません。次に動作が変わっていないことを確認します:
cargo test
.clone()を追加した場合はClippyも実行してください:
cargo clippy
Clippyは不要なクローンにフラグを立てます。大きなVecや文字列では、冗長なクローンが気づかないうちに深刻なパフォーマンス問題になる可能性があります。
クイック判断ガイド
- 実行時に参照が実際には重複しない場合? → 不変借用が先に終了するよう再構成する(方法1)。
- 古い値のスナップショットが必要な場合? → クローンする(方法2)。
- 変更中のコレクションにインデックスアクセスする場合? → 参照ではなくインデックスを保存する(方法3)。
- 共有所有権やグラフ構造の場合? →
RefCellまたはArc<Mutex>(方法4)。 - 構造体メソッド内での競合の場合? → 個別のフィールドを借用する(方法5)。

