Zum Inhalt springen

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.)

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

Auf 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}");
}

Der Compiler zeigt auf drei Stellen, und zusammen erzählen sie die ganze Geschichte:

  1. Zeile 4: deposit wird eine veränderbare Ausleihe von balance.
  2. Zeile 5: withdraw soll eine zweite veränderbare Ausleihe von balance werden.
  3. Zeile 7: deposit wird 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.

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.

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: 120
▶ Selbst ausprobieren

Oft 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"]
▶ Selbst ausprobieren

(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 Punkte
▶ Selbst ausprobieren

Lö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]
▶ Selbst ausprobieren

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.

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: 120
▶ Selbst ausprobieren

Sei 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 borrowed

Die 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.

  • &mut in einer Schleife speichern. handles.push(&mut counter) in einer for-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 &mut auf denselben Wert hinzu. Lösung: Speichere Indizes oder eigene Werte, oder ändere den Wert direkt in der Schleife.
  • Zwei get_mut-Aufrufe auf einer HashMap. let a = stock.get_mut("apples") und danach let p = stock.get_mut("pears") leiht stock zweimal aus. Lösung: Werde mit a fertig, bevor du get_mut erneut aufrufst, oder benutze stock.get_disjoint_mut(["apples", "pears"]), um beide auf einmal zu bekommen.
  • Zwei Closures, die dieselbe Variable ändern. let mut inc = || count += 1; und let 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.

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