Zum Inhalt springen

Rust-Fehler E0502: cannot borrow as mutable because it is also borrowed as immutable

E0502 bedeutet: Du wolltest einen Wert ändern, während ein anderer Teil deines Codes ihn noch über eine Referenz gelesen hat. Das passiert ständig mit Vektoren, und meistens musst du nur eine Zeile verschieben oder einen Wert kopieren. Diese Seite zeigt dir zuerst die Lösung und erklärt dann, warum Rust darauf besteht. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &v[0]; // eine Referenz in den Vec hinein
| - immutable borrow occurs here
4 | v.push(4); // ❌ ändert den Vec, während `first` noch lebt
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("First: {first}"); // ...weil `first` hier benutzt wird
| ----- immutable borrow later used here

Auf Deutsch: „v kann nicht veränderbar ausgeliehen werden, weil es auch unveränderbar ausgeliehen ist.“ Die drei Markierungen zeigen, wo die lesende Ausleihe beginnt, wo die schreibende passiert und wo die lesende später noch benutzt wird.

Dieses Programm erzeugt den Fehler:

fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0]; // eine Referenz in den Vec hinein
v.push(4); // ❌ ändert den Vec, während `first` noch lebt
println!("First: {first}"); // ...weil `first` hier benutzt wird
}

Der Compiler zeigt auf drei Zeilen, und du brauchst alle drei, um den Fehler zu verstehen:

  1. Zeile 3: &v[0] erzeugt eine Referenz zum Lesen in v hinein. Das ist die unveränderbare Ausleihe (immutable borrow).
  2. Zeile 4: v.push(4) muss v ändern und braucht dafür eine veränderbare Ausleihe (mutable borrow).
  3. Zeile 5: first wird noch einmal benutzt. Genau diese Zeile hält die erste Ausleihe über Zeile 4 hinweg am Leben.

Ohne Zeile 5 würde der Code kompilieren. Das Problem ist nicht, dass es beide Ausleihen gibt, sondern dass sie sich überschneiden.

Die Regel lautet: viele Leser oder ein Schreiber, aber nie beides gleichzeitig. Du kennst sie schon von der Seite Borrowing.

Bei einem Vec gibt es dafür einen ganz konkreten Grund. Ein Vec speichert seine Elemente in einem zusammenhängenden Speicherblock. Wenn du push aufrufst und der Block voll ist, zieht der Vec alles in einen neuen, größeren Block um und gibt den alten frei. Jede Referenz in den alten Block (wie first) zeigt dann auf freigegebenen Speicher. In C++ ist das der berüchtigte Bug „Iterator Invalidation“. Er kann dein Programm abstürzen lassen oder still Datenmüll lesen.

Das ist, als würdest du dir die Adresse eines Freundes aufschreiben, und dann zieht er in ein größeres Haus. Dein Zettel sieht noch gut aus, zeigt aber auf ein leeres Gebäude. Rust weigert sich, den Code zu kompilieren, statt dich zu dem leeren Gebäude fahren zu lassen.

Probier das zuerst. Seit Rust 2018 endet eine Ausleihe bei ihrer letzten Benutzung, nicht erst an der schließenden Klammer. Das nennt man Non-Lexical Lifetimes. Benutze die Referenz also einfach zu Ende, bevor du den Vektor änderst:

fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("Erstes: {first}"); // letzte Benutzung von `first`: hier endet die Ausleihe
v.push(4); // ✅ niemand liest mehr
println!("Jetzt: {:?}", v);
}

Ausgabe:

Erstes: 1
Jetzt: [1, 2, 3, 4]
▶ Selbst ausprobieren

Nimm diese Lösung, wenn du den alten Wert auch nach der Änderung noch brauchst. Nimm dir eine Kopie statt einer Referenz. Bei Zahlen lässt du einfach das & weg. Bei einem String benutzt du .clone():

fn main() {
let mut queue = vec![String::from("deploy"), String::from("test")];
let next = queue[0].clone(); // unsere eigene Kopie, keine Referenz
queue.push(String::from("release"));
println!("Als Nächstes: {next}");
println!("Warteschlange: {:?}", queue);
}

Ausgabe:

Als Nächstes: deploy
Warteschlange: ["deploy", "test", "release"]
▶ Selbst ausprobieren

Mit let first = v[0]; bekommst du bei einem Vec<i32> die Kopie gratis, weil i32 Copy ist.

Lösung 3: Elemente mit iter_mut oder retain direkt ändern

Abschnitt betitelt „Lösung 3: Elemente mit iter_mut oder retain direkt ändern“

Nimm diese Lösung, wenn du eine Schleife wie for p in prices.iter() geschrieben hast und darin prices.remove(i) aufrufst oder Elemente änderst. Auch das ergibt E0502. Die Standardbibliothek hat genau dafür passende Methoden:

fn main() {
let mut prices = vec![120, 45, 300, 80];
// jedes Element direkt ändern
for p in prices.iter_mut() {
*p = *p * 9 / 10; // 10 % Rabatt
}
// die Elemente entfernen, die du nicht willst
prices.retain(|&p| p >= 50);
println!("{:?}", prices);
}

Ausgabe:

[108, 270, 72]
▶ Selbst ausprobieren

iter_mut gibt dir ein &mut pro Element, es gibt also immer nur einen Schreiber. retain übernimmt das Entfernen für dich – sicher und in einem Durchgang. Siehe Vektoren und Iteratoren.

Nimm diese Lösung, wenn du auf Grundlage dessen, was du liest, Elemente hinzufügen willst. Erst lesen und sammeln, dann in einem eigenen Schritt schreiben:

fn main() {
let mut scores = vec![10, 25, 7, 40];
let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect();
scores.extend(bonuses); // die lesende Ausleihe ist vorbei, also dürfen wir schreiben
println!("{:?}", scores);
}

Ausgabe:

[10, 25, 7, 40, 50, 80]
▶ Selbst ausprobieren

Der zusätzliche Vec kostet ein bisschen Speicher, aber der Code ist klar und offensichtlich korrekt. Meistens ist das ein guter Tausch.

Nimm das als letzten Ausweg, wenn die anderen Lösungen nicht passen. Ein Index ist nur eine Zahl, keine Ausleihe, und blockiert deshalb keine Änderungen:

fn main() {
let mut scores = vec![10, 25, 7];
for i in 0..scores.len() {
if scores[i] > 20 {
scores.push(scores[i] * 2);
}
}
println!("{:?}", scores);
}

Ausgabe:

[10, 25, 7, 50]
▶ Selbst ausprobieren

0..scores.len() wird einmal berechnet, bevor die Schleife startet. Die neuen Elemente werden also nicht mehr besucht. Vorsicht, wenn du per Index Elemente entfernst: Die Positionen verschieben sich, und du überspringst Elemente oder löst eine Panic aus. Zum Entfernen ist retain (Lösung 3) sicherer.

In einen Vec pushen, während du über ihn läufst. for s in &scores { scores.push(..) } leiht scores für die ganze Schleife aus. Lösung: erst sammeln und danach extend (Lösung 4), oder per Index durchlaufen (Lösung 5).

Eine &mut self-Methode aufrufen, während du über ein Feld läufst. Das passiert sehr oft in impl-Blöcken:

fn audit(&mut self) {
for item in &self.items {
self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable
}
}

self.record leiht sich das ganze self veränderbar aus, einschließlich self.items. Lösung: Greif direkt auf das andere Feld zu (self.log.push(item.clone())), denn Rust erlaubt es, zwei verschiedene Felder gleichzeitig auszuleihen. Oder klone die Elemente vorher. Siehe Structs.

Einen Wert aus einer HashMap festhalten und dabei einfügen. let apples = stock.get("apples").unwrap();, dann stock.insert(..) und danach apples benutzen ergibt E0502. Lösung: Kopier den Wert heraus, mit let apples = *stock.get("apples").unwrap(); oder .copied(). Siehe HashMap in Rust.

  • E0499: zwei veränderbare Ausleihen zur gleichen Zeit. Dieselbe Idee, nur mit zwei Schreibern statt einem Leser und einem Schreiber.
  • E0382: Du hast einen Wert verschoben und danach benutzt. Taucht oft auf, wenn du E0502 behebst, indem du verschiebst statt ausleihst.
  • E0106: eine fehlende Lifetime-Angabe bei einer Funktion, die eine Referenz zurückgibt.
  • Alle Compilerfehler: die vollständige Liste der Fehlerseiten.

Kurzer Check

1. Warum kompiliert let first = &v[0]; v.push(4); println!("{first}"); nicht?

2. Du willst jedes Element unter 50 aus einem Vec<i32> entfernen. Was ist die sauberste Lösung für E0502?

3. Wann endet eine unveränderbare Ausleihe im modernen Rust?

Punkte: 0 / 3