mut ist eines der ersten Sprachelemente, das Rust-Einsteiger ausprobieren — und gleichzeitig eines der am häufigsten missverstandenen. „mut macht eine Variable veränderbar" stimmt im Großen, verfehlt aber drei wichtige Unterscheidungen: die zwischen mut binding (let mut), mutable reference (&mut) und interior mutability (Cell, RefCell, Mutex). Wer alle drei sauber trennt, hat 90 % der späteren Ownership- und Borrow-Probleme gedanklich schon gelöst.
Der Default ist Immutability
Anders als praktisch jede andere Mainstream-Sprache verbietet Rust standardmäßig jede Mutation:
fn main() {
let zahl = 5;
zahl = 6; // Fehler E0384
}Die Begründung ist dreiteilig:
- Lesbarkeit. Wer eine Bindung ohne
mutsieht, weiß: dieser Wert wird sich im weiteren Code nicht ändern. Das spart mentale Last bei jeder Codereview. - Compiler-Optimierungen. Wenn der Compiler weiß, dass ein Wert nie mutiert wird, kann er ihn aggressiver inline-optimieren, Konstanten-Faltung anwenden und manchmal sogar Heap-Allokationen wegoptimieren.
- Korrektheits-Garantie für Borrow Checker. Eine Bindung ohne
mutkann der Compiler beliebig oft mit&-Referenzen geteilt werden lassen, weil garantiert nicht parallel geschrieben wird. Das ist die Grundlage für Datenrace-Freiheit.
Mit mut erlaubst du explizit Mutation:
fn main() {
let mut zahl = 5;
zahl = 6;
zahl += 1;
println!("{zahl}"); // 7
}Was mut wirklich tut
let mut x = ...; erlaubt drei Operationen, die ohne mut verboten sind:
- Re-Assignment der Bindung:
x = neuer_wert;. - Mutation durch Methoden, die
&mut selfnehmen:x.push(...),x.clear(),x.sort()und so weiter. - Erstellen einer mutable Referenz:
let r = &mut x;.
Was mut nicht tut: es macht den Wert selbst nicht „mutabler" im physischen Sinne. Der Wert lebt einfach in einem Speicher-Slot, in den hinein geschrieben werden darf.
Methoden mit &mut self
Sehr häufige Quelle für Verwirrung:
fn main() {
let v = vec![1, 2, 3];
v.push(4); // Fehler E0596 — v ist nicht mutable
}Vec::push hat die Signatur fn push(&mut self, value: T). Um sie aufzurufen, braucht v eine &mut-Referenz auf sich selbst. Die kann der Compiler nur dann konstruieren, wenn die Bindung mut ist.
Lösung: let mut v = vec![1, 2, 3];.
Drei Ebenen der Mutabilität
Das größte Aha-Erlebnis bei Rust kommt, wenn man die drei Mutabilitäts-Ebenen sauber auseinanderhalten kann.
Ebene 1: mut binding
Die Bindung darf neu zugewiesen werden:
let mut name = String::from("Alice");
name = String::from("Bob"); // ok: neuer Wert in die BindungEbene 2: &mut reference
Eine Referenz, durch die der Wert mutiert werden darf:
fn anhaengen(text: &mut String, suffix: &str) {
text.push_str(suffix);
}
fn main() {
let mut s = String::from("Hallo");
anhaengen(&mut s, ", Welt!");
println!("{s}");
}Wichtig: die Bindung s muss mut sein, und die Referenz muss &mut sein. Beides erforderlich. Eine &mut-Referenz von einer non-mut Bindung zu nehmen, ist verboten.
Ebene 3: Interior Mutability
Manchmal willst du einen Wert mutieren, obwohl du nur eine &-Referenz darauf hast — z. B. wenn der Wert in mehreren Stellen geteilt wird und Mutation kontrolliert geschehen muss. Dafür gibt es Interior Mutability über Typen wie Cell<T>, RefCell<T>, Mutex<T>, RwLock<T>, OnceLock<T>:
use std::cell::RefCell;
fn main() {
let zaehler = RefCell::new(0);
// &-Referenz, aber wir können trotzdem mutieren:
*zaehler.borrow_mut() += 1;
*zaehler.borrow_mut() += 1;
println!("{}", zaehler.borrow()); // 2
}Die Mutabilität ist „ins Innere" des Typs verschoben — der äußere Typ wirkt immutable, der Inhalt kann aber kontrolliert mutiert werden. Bei RefCell werden die Borrow-Regeln zur Laufzeit geprüft statt zur Compile-Zeit. Im Multithread-Kontext nutzt man Mutex oder RwLock, die zusätzlich Thread-Safety garantieren.
Interior Mutability bekommt einen eigenen Artikel im Kapitel Smart Pointers und Interior Mutability. Hier reicht es zu wissen: mut ist nicht die einzige Form der Mutation.
mut in Pattern-Bindings
mut kann auch in Patterns vorkommen — etwa wenn du ein Tupel destrukturierst und ein Teil davon mutable sein soll:
fn main() {
let (mut zaehler, name) = (0, "Lina");
zaehler += 1;
// name = "Tom"; // Fehler — name ist nicht mut
}Jede Bindung wird einzeln markiert. Ebenso bei Struct-Patterns:
struct Position { x: f64, y: f64 }
fn main() {
let p = Position { x: 0.0, y: 0.0 };
let Position { mut x, y } = p;
x = 5.0; // ok
// y = 5.0; // Fehler
}mut bei Funktions-Parametern
Auch Parameter können mut markiert sein:
fn inkrement(mut wert: i32) -> i32 {
wert += 1;
wert
}Hier wird der Parameter mutable, nicht der Originalwert. wert ist eine Kopie (für Copy-Typen) oder ein gemoveter Besitz (für nicht-Copy-Typen) — Änderungen an wert bleiben in der Funktion.
Das ist nicht das gleiche wie fn inkrement(wert: &mut i32) { *wert += 1; }, das den Originalwert über die Referenz mutiert.
mut bei Methoden auf Werten
Eine subtile, aber häufige Stolperfalle: der Compiler erlaubt manchmal Methodenaufrufe auf nicht-mut-Bindungen, die intern doch eine Mutation erfordern — über Auto-Deref-Coercion und Receiver-Konvertierung.
fn main() {
let mut wert = String::from("Hallo");
let referenz = &mut wert;
referenz.push_str(" Welt!");
println!("{}", wert);
}Hier ist referenz eine immutable Bindung an eine &mut String-Referenz. Du darfst die Referenz nicht neu zuweisen, aber durch sie hindurch mutieren — denn die Mutabilität sitzt im Typ der Referenz (&mut String), nicht in der Bindung.
Faustregel:
- Bindungs-Mutabilität (
let mut x = ...) — wer darf die Bindung neu beschreiben. - Referenz-Mutabilität (
&mut T) — was durch eine Referenz hindurch erlaubt ist.
Beide sind unabhängig voneinander.
Warum gibt es überhaupt Default-Immutability?
Drei Sätze, drei Antworten:
- Borrow Checker. Eine non-mut Bindung kann beliebig oft als shared
&-Referenz geteilt werden, ohne dass jemand parallel schreibt. Genau das ermöglicht Data-Race-Freiheit zur Compile-Zeit. - Optimierung. rustc kann einen non-mut Wert in Registern halten, in den Code inlinen oder Lifetime-Cleanup-Pfade vereinfachen, wenn er sicher ist, dass nichts mutiert wird.
- Klarheit. Ein Programm, in dem 80 % der Bindungen ohne
mutsind, hat eine sichtbare Eigenschaft: die meisten Werte sind „read-only". Wer Mutation sehen will, sucht diemuts — und damit auch die interessanten Stellen.
Häufige Stolperfallen
let mut x hilft nicht, wenn die Methode &self nimmt.
Häufige Verwechslung: jemand schreibt let mut s = String::from("a") und versucht, in einer Methode mit Signatur fn anhaengen(&self, ...) zu mutieren. Das geht nicht. Die Methode bekommt eine shared Referenz; in der Methode ist self nicht mutable. Lösung: die Methode auf &mut self umstellen.
Eine Methode auf &mut self braucht mut auf der Bindung UND eine &mut-Referenz.
v.push(x) mit Vec::push(&mut self, value: T) kann nur dann aufgerufen werden, wenn v per mut-Bindung deklariert ist (sonst gibt es keine &mut v). Wer v als &Vec<T>-Parameter bekommt, kann push nicht aufrufen — auch nicht durch let mut local_v = v (das würde nur eine zweite Bindung anlegen, nicht das Original mutieren).
mut-Bindung ≠ Interior Mutability.
RefCell<T> umgeht die Compile-Zeit-Borrow-Regeln und prüft sie zur Laufzeit. Wer sie missbraucht (zwei gleichzeitige borrow_mut()), bekommt einen Panic statt einen Compile-Fehler. Interior Mutability ist mächtig, aber ein Werkzeug für gezielte Anwendungsfälle — nicht der „einfachere Weg um den Borrow Checker".
Der mut-Warning-Hinweis ist gold wert.
Wenn du let mut x = ... schreibst und x nie mutierst, warnt der Compiler: variable does not need to be mutable. Genau hinhören — wer überflüssige muts im Code hat, signalisiert eine Mutation, die nie stattfindet. Das schadet der Lesbarkeit.
Closures fangen mut implizit ein.
Wenn eine Closure einen Wert mutiert, fängt sie ihn automatisch als FnMut-Closure ein. Du brauchst die Closure-Variable selbst dann mut: let mut zaehler = || count += 1;. Sonst kannst du sie nicht aufrufen, weil ihr Aufruf eine mutable Referenz auf sich selbst braucht.
const ist nicht „mut entgegengesetzt“.
const ist eine andere Bindungsart — Compile-Zeit-Wert, kein Speicher-Slot, keine Address-Operation. Wer „eine unveränderliche Konstante" braucht, nimmt entweder let (lokal, immutable per default) oder const (compile-time, global / lokal beides möglich). Mehr im Artikel zu const vs. static.
mut färbt nicht die Welt drumherum.
let mut s = vec![1,2,3] macht s veränderbar — Elemente in s sind aber unabhängig davon nicht „automatisch mut". Wenn du in einem Iterator über s.iter() läufst, sind die Elemente &i32, nicht &mut i32. Für mutable Iteration: s.iter_mut().
Weiterführende Ressourcen
Externe Quellen
- The Rust Book – Variables and Mutability
- Rust Reference – Mutability
- The Rust Book – References and Borrowing
- std::cell::RefCell
- rustc Error E0384
- rustc Error E0596