Rust-Fehler E0594: cannot assign, as it is not declared as mutable (ohne mut)
E0594 bedeutet: Du hast irgendwas = wert geschrieben (oder +=, -=, …), aber dieses „irgendwas“ darfst du nur lesen. Meistens ist es ein Feld eines Structs in einer Variablen ohne mut, oder ein Wert, an den du über eine &-Referenz kommst. Der Fehler ist sehr häufig, und die Lösung ist fast immer ein einziges Wort: mut. Diese Seite zeigt dir zuerst die Lösung und erklärt sie dann. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“error[E0594]: cannot assign to `p.hp`, as `p` is not declared as mutable --> src/main.rs:8:5 |8 | p.hp = 5; // ❌ `p` ist schreibgeschützt | ^^^^^^^^ cannot assign |help: consider changing this to be mutable |7 | let mut p = Player { name: String::from("Ferris"), hp: 10 }; | +++Auf Deutsch heißt das: „p.hp kann nichts zugewiesen werden, weil p nicht als veränderbar deklariert ist.“ Dieses Programm erzeugt die Meldung:
struct Player { name: String, hp: u32,}
fn main() { let p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; // ❌ `p` ist schreibgeschützt println!("{} has {} hp", p.name, p.hp);}Oft siehst du auch eine zweite Formulierung: „cannot assign to *x, which is behind a & reference“ (*x kann nichts zugewiesen werden, weil es hinter einer &-Referenz liegt). Derselbe Fehlercode, eine etwas andere Ursache: Dort hältst du eine &-Referenz in der Hand, und mit einer &-Referenz darfst du nur hinschauen. Beide Fälle behandeln wir unten.
Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Schau dir die ^^^^^^^^ unter p.hp = 5 in Zeile 8 an. Du änderst ein Feld von p. Aber p wurde mit einem einfachen let angelegt. Damit ist das ganze Struct schreibgeschützt – und mit ihm jedes Feld darin.
Die help:-Zeile zeigt die Lösung: let mut p. Die +++ markieren die drei Zeichen, die du ergänzen musst.
Bei der zweiten Formulierung zeigt der Compiler auf eine Zeile wie *x += 1 und ergänzt `x` is a `&` reference, so it cannot be written to (x ist eine &-Referenz, also kann man nicht hineinschreiben). Du änderst dann keine Variable, die dir gehört. Du versuchst, den Wert von jemand anderem über eine ausgeliehene, schreibgeschützte Referenz zu ändern.
Stell dir ein Buch aus der Bibliothek vor. Du darfst darin lesen, so viel du willst, aber du darfst nicht hineinschreiben. Für Notizen brauchst du dein eigenes Exemplar (let mut) oder die Erlaubnis der Besitzerin (&mut).
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“In Rust ist alles schreibgeschützt, solange du nichts anderes sagst. Eine Variable braucht let mut, damit sie sich ändern darf, und eine Referenz muss &mut sein, damit du ändern darfst, worauf sie zeigt. Siehe Ein Struct veränderbar machen und Veränderbare Referenzen.
In Rust gibt es kein „veränderbares Feld“. Die Veränderbarkeit gehört zur Variablen (oder zur Referenz), nicht zu einzelnen Feldern. let mut p macht also jedes Feld von p veränderbar, und ein einfaches let p macht keines davon veränderbar. Das hält die Regel einfach: Du schaust nach, wie ein Wert deklariert wurde, und weißt, ob er sich ändern darf.
Die Regel für Referenzen schützt dich vor Überraschungen. Übergibst du &player an eine Funktion, verspricht ihre Signatur, dass sie nur liest. Ohne dieses Versprechen könnte jede Funktion heimlich deine Daten ändern. Und der Compiler könnte nicht verhindern, dass jemand liest und jemand anderes gleichzeitig schreibt (genau darum geht es bei E0502).
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“Lösung 1: mut an die Variable schreiben
Abschnitt betitelt „Lösung 1: mut an die Variable schreiben“Nimm diese Lösung, wenn das Struct dir gehört und du seine Felder ändern willst. Meistens ist das die richtige Lösung.
struct Player { name: String, hp: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; p.hp += 3; println!("{} hat {} HP", p.name, p.hp);}Ausgabe:
Ferris hat 8 HPDas Gleiche gilt für Tupel (pair.0 = 1;) und Arrays (grid[2] = 7;): Die Variable, die sie hält, braucht mut. Bei einem Struct in einem Vec braucht team[0].hp = 10; ein let mut team. Dieser Fall wird als E0596 gemeldet, aber die Lösung ist dieselbe.
Weist du nur einmal etwas zu, brauchst du vielleicht gar kein mut: Bau das Struct gleich mit dem richtigen Wert, etwa Player { hp: 5, ..other }.
Lösung 2: Mit &mut v oder iter_mut() durchlaufen
Abschnitt betitelt „Lösung 2: Mit &mut v oder iter_mut() durchlaufen“Nimm diese Lösung, wenn du über eine Sammlung läufst und jedes Element ändern willst. for x in &v und v.iter() liefern &-Referenzen, und die sind schreibgeschützt:
fn main() { let scores = vec![10, 20, 30]; for x in &scores { *x += 1; // ❌ `x` ist ein `&i32` } println!("{scores:?}");}error[E0594]: cannot assign to `*x`, which is behind a `&` reference --> src/main.rs:4:9 |3 | for x in &scores { | ------- this iterator yields `&` references4 | *x += 1; // ❌ `x` ist ein `&i32` | ^^^^^^^ `x` is a `&` reference, so it cannot be written to |help: use a mutable iterator instead |3 | for x in &mut scores { | +++Der Compiler sagt: Dieser Iterator liefert &-Referenzen, nimm stattdessen einen veränderbaren Iterator. Lauf also über &mut scores (oder ruf .iter_mut() auf) und mach den Vektor selbst mut:
fn main() { let mut scores = vec![10, 20, 30]; for x in &mut scores { *x += 1; } println!("{scores:?}");
for x in scores.iter_mut() { *x *= 2; } println!("{scores:?}");}Ausgabe:
[11, 21, 31][22, 42, 62]&mut scores und scores.iter_mut() machen dasselbe. Nimm, was sich besser liest. Musst du die Liste nicht direkt ändern, kannst du auch eine neue bauen: let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect();. Siehe Iteratoren.
Lösung 3: &mut self oder einen &mut-Parameter verwenden
Abschnitt betitelt „Lösung 3: &mut self oder einen &mut-Parameter verwenden“Nimm diese Lösung, wenn eine Methode oder Funktion Daten ändert, die sie bekommen hat. Eine Methode mit &self darf das Struct nur lesen, deshalb schlägt die Zuweisung an ein Feld fehl:
impl Account { fn deposit(&self, amount: u64) { self.balance += amount; // ❌ `self` ist eine `&`-Referenz }}error[E0594]: cannot assign to `self.balance`, which is behind a `&` reference --> src/main.rs:7:9 |7 | self.balance += amount; | ^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so it cannot be written to |help: consider changing this to be a mutable reference |6 | fn deposit(&mut self, amount: u64) { | +++Eine normale Funktion mit einem &-Parameter liefert denselben Fehler: fn reset(score: &u32) { *score = 0; } scheitert mit cannot assign to `*score`, which is behind a `&` reference. Die Lösung ist in beiden Fällen gleich: Schreib &mut in die Signatur, mach die Variable des Aufrufers zu let mut und übergib beim Aufruf &mut.
struct Account { balance: u64,}
impl Account { fn deposit(&mut self, amount: u64) { self.balance += amount; }}
fn reset(score: &mut u32) { *score = 0;}
fn main() { let mut acc = Account { balance: 0 }; acc.deposit(50); acc.deposit(25); println!("Kontostand: {}", acc.balance);
let mut score = 42; reset(&mut score); println!("Punktestand: {score}");}Ausgabe:
Kontostand: 75Punktestand: 0Bei Methoden schreibt Rust das &mut beim Aufruf für dich: acc.deposit(50) leiht acc von selbst veränderbar aus, solange acc mit let mut angelegt ist. Bei Funktionen schreibst du es selbst: reset(&mut score). Übergibst du &score an eine Funktion, die &mut u32 will, bekommst du E0308 (types differ in mutability, die Typen unterscheiden sich in der Veränderbarkeit). Siehe Verhalten mit Methoden hinzufügen.
Lösung 4 (letzter Ausweg): Cell oder RefCell
Abschnitt betitelt „Lösung 4 (letzter Ausweg): Cell oder RefCell“Nimm diese Lösung nur, wenn du an kein &mut herankommst: zum Beispiel, wenn du einen Trait implementierst, dessen Methode fest &self vorgibt, oder wenn viele Teile des Programms sich einen Wert über Rc teilen. Cell und RefCell erlauben Änderungen über eine &-Referenz („Interior Mutability“, innere Veränderbarkeit):
use std::cell::{Cell, RefCell};
struct Catalog { items: Vec<String>, lookups: Cell<u32>, history: RefCell<Vec<String>>,}
impl Catalog { fn contains(&self, name: &str) -> bool { self.lookups.set(self.lookups.get() + 1); self.history.borrow_mut().push(name.to_string()); self.items.iter().any(|item| item == name) }}
fn main() { let catalog = Catalog { items: vec![String::from("Apfel"), String::from("Birne")], lookups: Cell::new(0), history: RefCell::new(Vec::new()), }; println!("Birne? {}", catalog.contains("Birne")); println!("Kiwi? {}", catalog.contains("Kiwi")); println!("Abfragen: {}", catalog.lookups.get()); println!("Verlauf: {:?}", catalog.history.borrow());}Ausgabe:
Birne? trueKiwi? falseAbfragen: 2Verlauf: ["Birne", "Kiwi"]Cell ist für kleine Copy-Werte wie Zähler gedacht, RefCell für alles andere. Sei dir über den Preis im Klaren: RefCell verlegt die Borrow-Prüfung von der Kompilierzeit in die Laufzeit, und es löst eine Panic aus, wenn du zweimal gleichzeitig veränderbar ausleihst. Wenn ein einfaches &mut funktioniert, nimm lieber das. Zwischen Threads verwendest du stattdessen Mutex oder Atomics (siehe Shared State). Mehr dazu in RefCell.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“Einen Wert in einer HashMap ändern. *stock.get("apple").unwrap() += 1; scheitert mit cannot assign to data in a `&` reference, weil get eine &-Referenz zurückgibt. Lösung: *stock.get_mut("apple").unwrap() += 1; oder *stock.entry("apple").or_insert(0) += 1;. Siehe HashMaps.
Ein Closure, das als Fn übergeben wird und eine Variable ändert. run(|| count += 1) scheitert, wenn run ein F: Fn() nimmt, mit cannot assign to `count`, as it is a captured variable in a `Fn` closure (count ist eine eingefangene Variable in einem Fn-Closure). Lösung: Ändere den Bound zu F: FnMut() (und schreib mut f in run). Siehe Wenn du den falschen Bound wählst.
Ein globaler Zähler in einem static. static VISITS: u32 = 0; und dann VISITS += 1; scheitert mit cannot assign to immutable static item `VISITS`. Lösung: Nimm static VISITS: AtomicU32 = AtomicU32::new(0); und VISITS.fetch_add(1, Ordering::Relaxed); – oder, noch besser, reich einen Zähler als &mut weiter.
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- E0596: cannot borrow as mutable (kann nicht veränderbar ausgeliehen werden). Dasselbe fehlende
mut, aber bei einem Methodenaufruf wielist.push(x)statt bei einer Zuweisung. - E0384: cannot assign twice to immutable variable (zweimalige Zuweisung an eine unveränderbare Variable). Du weist einer ganzen Variablen etwas zu (
x = 5;), nicht einem Feld oder über eine Referenz. - E0308: mismatched types (unpassende Typen),
types differ in mutability– wenn du&xübergibst, wo&mut xerwartet wird. - E0502: Du hast
&mut-Zugriff, aber etwas anderes liest den Wert noch. - E0507: cannot move out of a value behind a reference (kein Move aus einem Wert hinter einer Referenz). Wieder die „nur lesen“-Regel, diesmal für Moves statt für Schreibzugriffe.
- Alle Compilerfehler: die vollständige Liste der Fehlerseiten.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. let user = User { age: 30 }; user.age = 31; ergibt E0594. Wie lautet die Lösung?
2. for p in &prices { *p += 1; } schlägt fehl. Was änderst du?
3. Eine Methode fn rename(&self, n: String) { self.name = n; } ergibt E0594. Was ist die übliche Lösung?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0594 – Rust error codes index (auf Englisch): die offizielle Erklärung dieses Fehlers.
- Strukturen definieren und instanziieren – Das Rust-Buch: warum die ganze Instanz veränderbar sein muss und nicht einzelne Felder.
- Referenzen und Borrowing – Das Rust-Buch: der Abschnitt „Veränderbare Referenzen“ erklärt
&im Vergleich zu&mut. - Methoden – Das Rust-Buch: wann eine Methode
&self,&mut selfoderselfbraucht. - RefCell<T> und das innere Veränderbarkeitsmuster – Das Rust-Buch: wie
RefCellÄnderungen über&erlaubt und was das zur Laufzeit kostet.