Wer in Rust produktiv werden will, muss eine Fertigkeit beherrschen, die in anderen Sprachen Nebensache ist: Fehlermeldungen lesen. Der rustc-Compiler ist gerade deshalb so streng, weil er dir mit jeder abgelehnten Compilation eine Lehre erteilt — Ownership-Verletzungen, fehlende Lifetimes, falsche Trait-Bounds. Die gute Nachricht: rustc-Diagnostik ist außergewöhnlich gut. Mit der richtigen Lesetechnik wird jedes Fehlerproblem zu einer Spur, die direkt zur Lösung führt. Dieser Artikel zerlegt eine rustc-Meldung in ihre Bauteile, geht die wichtigsten Error-Codes durch, zeigt, wann clippy mehr lehrt als rustc, und welche Werkzeuge dir im Hintergrund noch helfen.
Anatomie einer rustc-Fehlermeldung
Eine typische Meldung sieht so aus:
error[E0382]: borrow of moved value: `s`
--> src/main.rs:5:20
|
3 | let s = String::from("Hallo");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
4 | consume(s);
| - value moved here
5 | println!("{}", s);
| ^ value borrowed here after move
|
= note: `consume` takes ownership of `s` because `String` does not implement `Copy`
= help: consider cloning the value if the performance cost is acceptable: `s.clone()`Diese Meldung hat sieben strukturelle Bauteile, und jedes davon hilft:
errorvs.warning— der Schweregrad.warningführt nicht zum Build-Abbruch (außer du erzwingst es mit-D warnings).[E0382]— der Error-Code. Eindeutig pro Fehlerart, recherchierbar mitrustc --explain E0382.borrow of moved value: \s`` — die Hauptaussage in einem Satz. Was ist konzeptuell falsch.--> src/main.rs:5:20— die primäre Stelle: Datei, Zeile, Spalte.- Code-Snippet mit
^-Markern — der primary span. Die Stelle, an der der Fehler wirklich auftritt, plus Kontext-Zeilen. note:— Hintergrund. Warum genau der Compiler die Operation ablehnt.help:undsuggestion:— Lösungsvorschläge. Manchmal sogar mit konkretem Diff (note: this can be a Foo<T> if you do X).
Der entscheidende Reflex: immer von oben nach unten lesen. Erst der Error-Code-Satz, dann die Stelle, dann die note, dann die help. Oft braucht man die help-Zeile nicht — die note macht den Fehler schon verständlich.
Mit --explain mehr lernen
Für jeden Error-Code gibt es eine ausführliche Erklärung:
rustc --explain E0382Das öffnet eine mehrere Absätze lange Erklärung mit Beispiel-Code und Lösung. Bei Konzepten wie Ownership, Lifetimes oder Trait-Bounds extrem lehrreich — wer fünf solche Erklärungen liest, hat ein Drittel des Lernpensums hinter sich.
Die wichtigsten Error-Codes für Einsteiger
E0382 — borrow/use of moved value
Du hast einen Wert in eine Funktion (oder Variable) gemoved und versuchst danach, ihn nochmal zu verwenden.
fn main() {
let s = String::from("Hallo");
consume(s);
println!("{}", s); // E0382 — s wurde gemoved
}
fn consume(_text: String) {}Lösungen je nach Intent:
- Wenn die Funktion den Wert nur lesen muss: per Referenz übergeben (
consume(&s), Signaturfn consume(text: &String)). - Wenn beide den Wert brauchen: klonen (
consume(s.clone())) — verständlich teuer. - Wenn du tatsächlich Ownership übergeben willst und danach nicht mehr brauchst: den
println!weglassen.
E0502 — cannot borrow as mutable / as immutable
Du hast gleichzeitig eine shared (&) und eine mutable (&mut) Referenz im selben Scope.
fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0]; // shared borrow
v.push(4); // E0502 — mutable borrow nicht erlaubt
println!("{}", first);
}Rust verbietet Aliasing-und-Mutation gleichzeitig. Das ist der Kern der Memory-Safety-Garantie. Lösungen:
- Den shared-Borrow vor dem mutable-Aufruf droppen (z. B. den
println!davor verschieben, sodassfirstnachpushnicht mehr gebraucht wird — NLL erkennt das ab Edition 2018). - Den Wert klonen statt referenzieren:
let first = v[0].clone();.
E0507 — cannot move out of a borrowed value
Du versuchst, einen Wert aus einer Referenz herauszunehmen — was unmöglich ist, weil der Eigentümer dann ein leeres Loch hätte.
struct Container { data: String }
fn extract(c: &Container) -> String {
c.data // E0507 — data ist hinter &Container
}Lösungen:
- Klonen:
c.data.clone(). - Den Container by-value annehmen:
fn extract(c: Container) -> String { c.data }. - Bei mutable Borrow:
std::mem::take(&mut c.data)ersetzt mit dem Default-Wert.
E0596 — cannot borrow as mutable
Du versuchst, eine &mut-Referenz von einer immutable Variable zu nehmen.
fn main() {
let v = vec![1, 2, 3]; // ohne `mut`
v.push(4); // E0596 — v ist nicht mutable
}Lösung: let mut v = ...;. Der Compiler-Suggestion-Block sagt das auch direkt.
E0308 — mismatched types
Der Klassiker. Du hast einen Wert vom Typ A, der Compiler erwartet Typ B.
fn addiere(a: i32, b: i32) -> i32 {
a + b; // E0308 — Semikolon macht aus a+b den Wert ()
}Der note: implicitly returns () as its body has no tail expression weist dich auf das Semikolon hin.
Mehrere Fehler gleichzeitig
rustc bricht nicht beim ersten Fehler ab — er sammelt mehrere und zeigt sie zusammen. Mit cargo build siehst du oft fünf, zehn Diagnosen auf einmal.
Strategie:
- Immer von oben nach unten beheben. Spätere Fehler folgen oft aus früheren (eine fehlende Variable führt zu allen ihren Verwendungs-Stellen, die auch melden).
- Nach jedem Fix neu bauen. Nicht versuchen, drei Fehler gleichzeitig im Kopf zu lösen — der nächste Build zeigt dir, ob die Top-Fehler verschwunden sind und welche neuen Fehler aufgetaucht sind.
cargo checkstattcargo buildwährend du fixt. Faktor 5–10 schneller, weil keine Binary erzeugt wird.
warnings: ernst nehmen, ernst lassen
Warnings vom Compiler sind in Rust deutlich besser kuratiert als in C oder C++. Wenn rustc warnt, hat das meistens einen guten Grund. Häufige Beispiele:
unused_variables— du hast eine Variable angelegt, aber nie gelesen. Wenn das Absicht ist (z. B. bei einem Function-Parameter, der noch nicht genutzt wird), präfixe sie mit_:_unused.unused_must_use— du ignorierst einen Wert (typischerweise einResult), den der Compiler für wichtig hält. Lösung: entweder mitlet _ = ...explizit ignorieren oder behandeln.dead_code— eine Funktion oder ein Modul wird nirgendwo aufgerufen. Im Library-Code oft falsch positiv (sie wird ja von externen Crates aufgerufen) — dann mitpubmarkieren oder mit#[allow(dead_code)]explizit erlauben.
In CI: cargo build -- -D warnings macht alle Warnings zu Errors. Das verhindert, dass sich Warnings im Code anlagern.
Clippy — der Linter, der unterrichtet
cargo clippy ist mehr als ein Linter — es ist ein Lehrmittel. Über 700 Lints, jeder mit einer kurzen Erklärung, viele mit automatischer Korrektur.
cargo clippy # Standard-Lints
cargo clippy -- -W clippy::pedantic # Pedantischer Modus, viele Hinweise mehr
cargo clippy --fix # Wo möglich, automatisch fixenBeispiel-Lint:
warning: this `if let` can be collapsed into the outer `match`
--> src/main.rs:8:5
|
8 | if let Some(x) = opt { println!("{x}"); }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: replace it with this
|
8 | opt.map(|x| println!("{x}"));
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
= note: `#[warn(clippy::manual_map)]` on by defaultJeder Lint hat einen Namen (clippy::manual_map), eine Begründung im Lint-Verzeichnis und oft eine spielbare Korrektur. Du kannst einzelne Lints lokal abschalten:
#[allow(clippy::manual_map)]
fn main() {
// ...
}Für die Code-Qualität in einem Projekt ist es empfehlenswert, cargo clippy -- -D warnings in CI laufen zu lassen. Wer Clippy von Anfang an ernst nimmt, lernt schneller idiomatisches Rust als jeder, der nur rustc-Errors fixt.
rust-analyzer in der IDE
In VS Code, Neovim, Helix oder JetBrains übernimmt rust-analyzer die rustc-Diagnostik live während du tippst. Was er zeigt:
- Inline-Errors und -Warnings — kein Build nötig.
- Type-Hints: über jeder
let-Bindung kannst du den inferred Typ einblenden. - Inlay-Hints für Closure-Parameter, generische Argumente, Iterator-Adapter.
- Hover-Doku: Mauszeiger über einen Identifier zeigt seine Doc-Kommentare.
- Quick Fixes: viele rustc-Suggestions sind als Ein-Klick-Action verfügbar.
- Go-to-Definition auch in std hinein, wenn
rust-srcinstalliert ist.
Rust ohne rust-analyzer ist möglich, aber etwa wie TypeScript ohne tsserver — funktioniert, lässt aber viel Arbeitserleichterung liegen.
cargo expand — Makros sichtbar machen
Wenn du eine Makro-Expansion verstehen willst (z. B. was #[derive(Debug)] oder println! wirklich generiert), nutze das Tool cargo-expand:
cargo install cargo-expand # einmalig
cargo expand # Gesamtes Crate
cargo expand --bin meine-app
cargo expand --lib path::to::moduleSehr nützlich, wenn ein derive-Makro ungewöhnliches Verhalten zeigt oder du selbst proc-macros baust. Auch unentbehrlich beim Lesen von Crates, die intensiv mit Makros arbeiten (z. B. serde, tokio).
cargo check vs. cargo build im Workflow
Wiederholung, weil so wichtig:
| Befehl | Was er tut | Wann nutzen |
|---|---|---|
cargo check | Parsen, Typchecken, Borrow-Checken, kein Linking | Während der Entwicklung, in einer Watch-Loop |
cargo build | Wie check + Codegen + Linking | Wenn du eine Binary brauchst |
cargo clippy | Wie check + Lints | Vor jedem Commit |
cargo test | Build + Tests laufen | Vor jedem Commit |
Empfohlenes Live-Setup:
cargo install cargo-watch
cargo watch -x check # Bei jeder Datei-Änderung check
cargo watch -x check -x test # check + test
cargo watch -x 'clippy -- -D warnings' # Strenge Lintscargo-watch startet bei jeder Datei-Änderung neu — du tippst, speicherst, und siehst sofort, ob du etwas kaputt gemacht hast. Faktor 10 für die Lernkurve.
FAQ
Warum sind rustc-Meldungen so lang?
Weil sie dir den Kontext mitliefern. Andere Compiler sagen „type error" — rustc zeigt dir die Stelle, den Typ-Unterschied, oft den Grund (note) und meist sogar einen Fix (help). Was anfangs überwältigend wirkt, ist nach ein paar Wochen das Werkzeug, mit dem du schneller wirst.
Sollte ich unwrap() verwenden, weil der Compiler nicht meckert?
In Beispielen ja, in produktivem Code nein. unwrap() panickt zur Laufzeit, wenn der Option oder Result None/Err ist. Clippy warnt mit clippy::unwrap_used und clippy::expect_used — expect("Begründung") ist immerhin selbstdokumentierend, aber idealerweise behandelst du den Fehler-Fall explizit (siehe Error-Handling-Kapitel).
Was tun, wenn ich die Fehlermeldung nicht verstehe?
Drei Strategien: (1) rustc --explain E0xxx für die ausführliche Erklärung. (2) Den Error-Code googeln — die Rust-Community schreibt viel über die typischen Fallen. (3) Den Fall minimieren — strip alles raus, bis nur der Fehler übrig ist; oft erkennt man dann das Pattern.
Warum kompiliert es nach einem rustc-Update auf einmal nicht mehr?
Selten ein Sprach-Bruch — fast immer ein Lint, der von Warning zu Error befördert wurde, oder eine Dependency, die strenger geprüft wird. cargo build -- -W unstable-features zeigt unstable-Verwendungen. cargo update und das Cargo.lock neu schreiben löst manche Inkonsistenzen. Bei echten Sprach-Brüchen würde das Compatibility Promise verletzt sein — das ist äußerst selten.
Lints in Cargo.toml projektweit konfigurieren — geht das?
Ja. Das [lints]-Tabellen-Feature seit Rust 1.74 erlaubt projektweite Lint-Konfiguration im Manifest: [lints.rust] unused_imports = "deny" oder [lints.clippy] manual_map = "warn". Praktisch, weil sonst alle CI-Aufrufe Flag-Listen mitschleppen.
Sind die help:-Vorschläge immer richtig?
Meistens schon, aber nicht immer. Die help-Zeilen sind heuristisch — gerade bei komplexeren Lifetime- oder Generics-Fehlern schlägt rustc manchmal eine Lösung vor, die das Symptom heilt, aber nicht die Ursache. Verstehen, dann anwenden.
Wie zeige ich nur Errors, keine Warnings?
cargo build 2>&1 | grep -A 5 ^error filtert auf der Shell. In rust-analyzer kannst du die Diagnostik-Severity in den Einstellungen filtern. Für gezielte Suche nach einem bestimmten Code: cargo build 2>&1 | grep -A 10 E0382.
Was ist mit panic!-Meldungen zur Laufzeit?
Die unterscheiden sich grundsätzlich von Compile-Fehlern. Eine Panic-Meldung wie thread 'main' panicked at 'index out of bounds: ...' ist ein Runtime-Crash. Mit RUST_BACKTRACE=1 cargo run bekommst du den vollen Stack-Trace. Ein panic deutet meist auf einen logischen Fehler oder ein nicht behandeltes Edge-Case — der Borrow Checker findet ihn nicht im Voraus.
Weiterführende Ressourcen
Externe Quellen
- rustc Error Index – alle Error-Codes mit Erklärung
- The rustc Book – Lints
- Clippy Lint Verzeichnis
- rust-analyzer User Manual
- cargo-expand auf GitHub
- cargo-watch auf GitHub