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:

Rust Immutable per Default
fn main() {
    let zahl = 5;
    zahl = 6;             // Fehler E0384
}

Die Begründung ist dreiteilig:

  • Lesbarkeit. Wer eine Bindung ohne mut sieht, 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 mut kann 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:

Rust Mit mut
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:

  1. Re-Assignment der Bindung: x = neuer_wert;.
  2. Mutation durch Methoden, die &mut self nehmen: x.push(...), x.clear(), x.sort() und so weiter.
  3. 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:

Rust Vec braucht mut für push
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:

Rust mut binding
let mut name = String::from("Alice");
name = String::from("Bob");           // ok: neuer Wert in die Bindung

Ebene 2: &mut reference

Eine Referenz, durch die der Wert mutiert werden darf:

Rust &mut
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>:

Rust RefCell
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:

Rust mut im Pattern
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:

Rust mut in struct pattern
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:

Rust mut-Parameter
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.

Rust Klingt komisch, geht aber
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 mut sind, hat eine sichtbare Eigenschaft: die meisten Werte sind „read-only". Wer Mutation sehen will, sucht die muts — 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

/ Weiter

Zurück zu Variablen & Bindungen

Zur Übersicht