Rust-Fehler E0499: cannot borrow as mutable more than once at a time
Der Fehler E0499 bedeutet: Du wolltest zwei veränderbare Referenzen (&mut) auf denselben Wert zur gleichen Zeit haben. Das ist einer der häufigsten Fehler des Borrow Checkers, und die Lösung ist meistens klein: Oft musst du nur eine Zeile verschieben. (Suchst du einen anderen Fehler? Schau in die Übersicht der Compilerfehler.)
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“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 hereAuf Deutsch: „balance kann nicht mehr als einmal gleichzeitig veränderbar ausgeliehen werden.“ Die Markierungen zeigen die erste Ausleihe, die zweite und die Stelle, an der die erste später noch benutzt wird.
Dieses Programm erzeugt den Fehler:
fn main() { let mut balance = 100;
let deposit = &mut balance; let withdraw = &mut balance;
*deposit += 50; *withdraw -= 30;
println!("Balance: {balance}");}Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Der Compiler zeigt auf drei Stellen, und zusammen erzählen sie die ganze Geschichte:
- Zeile 4:
depositwird eine veränderbare Ausleihe vonbalance. - Zeile 5:
withdrawsoll eine zweite veränderbare Ausleihe vonbalancewerden. - Zeile 7:
depositwird nach Zeile 5 noch benutzt. Die erste Ausleihe lebt also noch, als die zweite beginnt.
Die dritte Markierung ist die wichtige. Zwei &mut-Ausleihen sind nur dann ein Problem, wenn sich ihre Lebensdauern überschneiden. Wäre deposit vor Zeile 5 fertig, gäbe es keinen Fehler.
Stell dir ein gemeinsames Dokument mit genau einem „Bearbeiten“-Schlüssel vor. Wer den Schlüssel hat, darf das Dokument ändern. Rust macht keine Kopie des Schlüssels: Die zweite Person muss warten, bis die erste ihn zurückgibt.
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“Die Regel für Borrowing (Ausleihen) in Rust lautet: Zu jedem Zeitpunkt darf es viele Leser (&) oder einen Schreiber (&mut) geben, aber nie mehr als einen Schreiber. Eine &mut-Referenz ist ein Versprechen: „Solange es mich gibt, fasst niemand sonst diesen Wert an.“
Dieses Versprechen verhindert echte Bugs. Könnten zwei Codestellen denselben Wert gleichzeitig ändern, könnte die eine die Größe eines Vec ändern, während die andere noch in seinen alten Speicher zeigt. In anderen Sprachen führt das zu einem Absturz oder zu still beschädigten Daten. Zwischen Threads ist es ein Data Race.
Dank dieser Regel weiß der Compiler sicher, dass ein &mut der einzige Weg zu den Daten ist, solange es lebt. Diese Sicherheit bekommst du ohne Kosten zur Laufzeit. Der Preis: Manchmal musst du dem Compiler deutlicher zeigen, dass deine zwei Ausleihen sich gar nicht in die Quere kommen.
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“Lösung 1: Die erste Ausleihe beenden, bevor die zweite beginnt
Abschnitt betitelt „Lösung 1: Die erste Ausleihe beenden, bevor die zweite beginnt“Probier das zuerst. Damit behebst du die meisten E0499-Fehler. Eine Ausleihe dauert bis zu ihrer letzten Benutzung, nicht bis zum Ende des Blocks. Erledige also alle Arbeit mit der ersten Referenz und erzeuge erst danach die zweite:
fn main() { let mut balance = 100;
let deposit = &mut balance; *deposit += 50; // letzte Benutzung von `deposit`: hier endet die Ausleihe
let withdraw = &mut balance; // ✅ gerade leiht sich niemand sonst etwas aus *withdraw -= 30;
println!("Kontostand: {balance}");}Ausgabe:
Kontostand: 120Oft brauchst du die zusätzlichen Variablen gar nicht: balance += 50; und danach balance -= 30; funktioniert genauso gut.
Lösung 2: Teile ausleihen, die sich nicht überschneiden
Abschnitt betitelt „Lösung 2: Teile ausleihen, die sich nicht überschneiden“Manchmal brauchst du wirklich zwei &mut gleichzeitig, aber sie zeigen auf verschiedene Teile der Daten. Das sieht der Compiler nicht immer, also zeigst du es ihm.
Bei zwei Elementen eines Vec oder Slice leihen tasks[0] und tasks[1] beide den ganzen tasks aus. Mit split_at_mut (auf Englisch) teilst du ihn in zwei Hälften, die sich garantiert nicht überschneiden:
fn main() { let mut tasks = vec![String::from("write"), String::from("test")];
// Den Vec in zwei Hälften teilen, die sich nicht überschneiden 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:?}");}Ausgabe:
["test", "write"](Nur zum Tauschen ist tasks.swap(0, 1) noch einfacher. split_at_mut ist für Fälle gedacht, in denen du beide Referenzen für echte Arbeit brauchst.)
Bei einem Struct tritt derselbe Fehler auf, wenn du zwei Methoden aufrufst, die jeweils &mut self nehmen, etwa p.name_mut() und p.score_mut(). Jeder Aufruf leiht sich das ganze Struct aus. Leihst du dir stattdessen direkt die Felder aus, sieht der Compiler, dass sie getrennt sind:
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), score: 0 };
// Die Felder direkt ausleihen, nicht über Methoden let name = &mut p.name; let score = &mut p.score;
name.push_str(" die Krabbe"); *score += 10; println!("{} hat {} Punkte", p.name, p.score);}Ausgabe:
Ferris die Krabbe hat 10 PunkteLösung 3: Indizes statt festgehaltener Referenzen
Abschnitt betitelt „Lösung 3: Indizes statt festgehaltener Referenzen“Ein Klassiker: Du läufst mit iter_mut() über einen Vec und willst in der Schleife auch noch push aufrufen. Der Iterator hält die ganze Schleife über ein &mut, also ist push ein zweites:
fn main() { let mut queue = vec![1, 2, 3]; for job in queue.iter_mut() { *job *= 10; queue.push(*job); // ❌ E0499: `queue` ist schon von iter_mut() ausgeliehen }}Wenn die Schleife die Sammlung selbst ändern muss, lauf stattdessen über die Positionen. Ein Index ist nur eine Zahl und leiht sich nichts aus:
fn main() { let mut queue = vec![1, 2, 3];
// Über Positionen laufen, statt eine Referenz in `queue` festzuhalten for i in 0..queue.len() { queue[i] *= 10; let job = queue[i]; queue.push(job); }
println!("{queue:?}");}Ausgabe:
[10, 20, 30, 10, 20, 30]0..queue.len() wird einmal berechnet, bevor die Schleife startet. Die neuen Elemente werden also nicht noch einmal besucht. Jede Ausleihe mit queue[i] dauert nur eine Zeile.
Lösung 4: RefCell (letzter Ausweg)
Abschnitt betitelt „Lösung 4: RefCell (letzter Ausweg)“RefCell verlegt die Borrow-Prüfung von der Kompilierzeit in die Laufzeit. Du kannst borrow_mut() ohne mut-Variable aufrufen, solange nie zwei veränderbare Ausleihen im selben Moment leben:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100); // Achtung: kein `mut` nötig
*balance.borrow_mut() += 50; // ausleihen, ändern, die Ausleihe endet beim `;` *balance.borrow_mut() -= 30;
println!("Kontostand: {}", balance.borrow());}Ausgabe:
Kontostand: 120Sei ehrlich zu dir: RefCell hebt die Regel nicht auf, es prüft sie nur später. Hältst du zwei borrow_mut()-Guards gleichzeitig am Leben, kompiliert das Programm zwar, löst aber beim Ausführen eine Panic aus:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100);
let mut deposit = balance.borrow_mut(); let mut withdraw = balance.borrow_mut(); // 💥 `deposit` lebt noch
*deposit += 50; *withdraw -= 30;}thread 'main' (12345) panicked at src/main.rs:7:32:RefCell already borrowedDie Panic-Meldung heißt: „RefCell ist schon ausgeliehen“ – in Zeile 7, beim zweiten borrow_mut().
Greif also nur zu RefCell, wenn die Daten wirklich geteilt sind (zum Beispiel in Rc<RefCell<T>>) und die Lösungen 1–3 nicht passen. Es ist kein Mittel, um den Compiler zum Schweigen zu bringen.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“&mutin einer Schleife speichern.handles.push(&mut counter)in einerfor-Schleife scheitert mit „mutably borrowed here in the previous iteration of the loop“ (im vorherigen Schleifendurchlauf hier veränderbar ausgeliehen). Jeder Durchlauf fügt ein weiteres&mutauf denselben Wert hinzu. Lösung: Speichere Indizes oder eigene Werte, oder ändere den Wert direkt in der Schleife.- Zwei
get_mut-Aufrufe auf einerHashMap.let a = stock.get_mut("apples")und danachlet p = stock.get_mut("pears")leihtstockzweimal aus. Lösung: Werde mitafertig, bevor duget_muterneut aufrufst, oder benutzestock.get_disjoint_mut(["apples", "pears"]), um beide auf einmal zu bekommen. - Zwei Closures, die dieselbe Variable ändern.
let mut inc = || count += 1;undlet mut inc2 = || count += 2;halten jeweils ein&mut count, solange sie leben. Lösung: Benutze einen einzigen Closure, oder ruf den ersten zum letzten Mal auf, bevor du den zweiten erzeugst. Siehe Closures.
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- E0502: cannot borrow as mutable because it is also borrowed as immutable: dieselbe Idee, aber eine der Ausleihen ist ein
&zum Lesen. - E0382: borrow of moved value: Du hast einen Wert benutzt, nachdem er woandershin verschoben wurde.
- E0106: missing lifetime specifier: Rust muss wissen, wie lange eine zurückgegebene Referenz lebt.
- Alle Compilerfehler: die vollständige Liste der Fehlerseiten.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Wann meldet Rust E0499?
2. Du erzeugst let a = &mut v;, benutzt a zum letzten Mal und erzeugst dann let b = &mut v;. Was passiert?
3. Mit welchem Werkzeug bekommst du zwei &mut-Referenzen auf verschiedene Elemente desselben Slice?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0499 – Rust error codes index (auf Englisch): die offizielle Erklärung dieses Fehlers, mit einem minimalen Beispiel.
- Veränderbare Referenzen – Das Rust-Buch: der Abschnitt, der die Regel „nur ein
&mutgleichzeitig“ einführt. - Splitting Borrows – The Rustonomicon (auf Englisch): warum sich Felder eines Structs getrennt ausleihen lassen, Elemente eines Slice aber nicht, und wie
split_at_mutfunktioniert. RefCell<T>und das innere Veränderbarkeitsmuster – Das Rust-Buch: wann es richtig ist, die Borrow-Prüfung in die Laufzeit zu verlegen.