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.
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“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 here4 | v.push(4); // ❌ ändert den Vec, während `first` noch lebt | ^^^^^^^^^ mutable borrow occurs here5 | println!("First: {first}"); // ...weil `first` hier benutzt wird | ----- immutable borrow later used hereAuf 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}Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Der Compiler zeigt auf drei Zeilen, und du brauchst alle drei, um den Fehler zu verstehen:
- Zeile 3:
&v[0]erzeugt eine Referenz zum Lesen invhinein. Das ist die unveränderbare Ausleihe (immutable borrow). - Zeile 4:
v.push(4)mussvändern und braucht dafür eine veränderbare Ausleihe (mutable borrow). - Zeile 5:
firstwird 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.
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“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.
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“Lösung 1: Erst fertig lesen, dann schreiben
Abschnitt betitelt „Lösung 1: Erst fertig lesen, dann schreiben“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: 1Jetzt: [1, 2, 3, 4]Lösung 2: Den Wert herauskopieren
Abschnitt betitelt „Lösung 2: Den Wert herauskopieren“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: deployWarteschlange: ["deploy", "test", "release"]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]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.
Lösung 4: Änderungen sammeln, dann anwenden
Abschnitt betitelt „Lösung 4: Änderungen sammeln, dann anwenden“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]Der zusätzliche Vec kostet ein bisschen Speicher, aber der Code ist klar und offensichtlich korrekt. Meistens ist das ein guter Tausch.
Lösung 5: Indizes benutzen
Abschnitt betitelt „Lösung 5: Indizes benutzen“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]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.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“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.
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- 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
Abschnitt betitelt „Kurzer Check“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
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0502 – Rust error codes index (auf Englisch): die offizielle Erklärung dieses Fehlers.
- Wertlisten in Vektoren ablegen – Das Rust-Buch: enthält genau dieses Beispiel
let first = &v[0]; v.push(..)und erklärt, warum es scheitert. - Referenzen und Borrowing – Das Rust-Buch: die Regel „viele Leser oder ein Schreiber“ im Detail.
- Non-lexical lifetimes – The Edition Guide (auf Englisch): warum eine Ausleihe jetzt bei ihrer letzten Benutzung endet statt an der schließenden Klammer.
Vec– Doku der Standardbibliothek (auf Englisch):retain,iter_mut,extendund die anderen Methoden, mit denen du einen Vektor sicher änderst.