Rust-Fehler E0382: borrow of moved value (verschobener Wert)
E0382 bedeutet: Du hast einen Wert weggegeben (du hast ihn verschoben, auf Englisch „moved“) und willst ihn danach noch einmal benutzen. Das ist der häufigste Fehler in der ersten Woche mit Rust, und fast immer lässt er sich mit einer einzigen Zeile beheben. Diese Seite zeigt dir zuerst die Lösung und erklärt dann, was dahintersteckt. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“error[E0382]: borrow of moved value: `name` --> src/main.rs:8:25 |6 | let name = String::from("Ferris"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait7 | greet(name); // hier wird name in greet verschoben | ---- value moved here8 | println!("Goodbye, {name}!"); // ❌ name ist weg | ^^^^ value borrowed here after move |note: consider changing this parameter type in function `greet` to borrow instead if owning the value isn't necessary --> src/main.rs:1:16 |1 | fn greet(name: String) { | ----- ^^^^^^ this parameter takes ownership of the valuehelp: consider cloning the value if the performance cost is acceptable |7 | greet(name.clone()); // hier wird name in greet verschoben | ++++++++„Borrow of moved value“ heißt so viel wie „Ausleihen eines verschobenen Werts“. Die note schlägt vor, dass greet den Wert nur ausleiht, statt ihn zu übernehmen. Das help schlägt vor, ihn zu klonen.
Dieses Programm erzeugt den Fehler:
fn greet(name: String) { println!("Hello, {name}!");}
fn main() { let name = String::from("Ferris"); greet(name); // hier wird name in greet verschoben println!("Goodbye, {name}!"); // ❌ name ist weg}Vielleicht siehst du auch die Formulierung „use of moved value“ (Benutzung eines verschobenen Werts). Das ist derselbe Fehler. Rust sagt „borrow“, wenn die spätere Zeile den Wert nur liest (wie println!), und „use“, wenn sie ihn noch einmal verschieben will (wie let other = name;).
Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Lies die drei Markierungen von oben nach unten. Sie erzählen die ganze Geschichte:
- Zeile 6:
nameist einString, undStringist nichtCopy. Wenn du ihn irgendwohin übergibst, wird er also verschoben. - Zeile 7:
greet(name)verschiebt den String in die Funktion. Jetzt gehört ergreet, und am Ende vongreetwird er aufgeräumt. - Zeile 8: Du willst
namenoch einmal lesen, aber er gehörtmainnicht mehr.
Stell dir vor, du gibst deinen einzigen Haustürschlüssel einem Freund. Danach kannst du die Tür damit nicht mehr aufschließen – auch wenn du dich noch genau erinnerst, dass du ihn mal hattest.
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“Jeder Wert in Rust hat genau einen Owner (Eigentümer). Verschwindet der Owner, wird der Wert aufgeräumt. So gibt Rust Speicher frei, ganz ohne Garbage Collector. Die Regeln dazu erklärt die Seite Ownership.
Würden zwei Variablen denselben String besitzen, würden am Ende beide versuchen, denselben Speicher freizugeben. Das nennt man „Double Free“ – ein echter Bug, der Programme in C und C++ abstürzen lässt. Rust verhindert ihn: Zuweisungen und Funktionsaufrufe verschieben den Wert, sodass es immer nur einen Owner gibt.
Kleine, einfache Typen wie i32, bool und char sind Copy: Wenn du sie übergibst, entsteht eine billige Kopie, und E0382 begegnet dir bei ihnen nie. Typen, die Speicher auf dem Heap besitzen, wie String, Vec und die meisten Structs, werden dagegen verschoben.
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“Lösung 1: Mit & ausleihen
Abschnitt betitelt „Lösung 1: Mit & ausleihen“Nimm diese Lösung, wenn die Funktion den Wert nur ansehen muss. In den meisten Fällen ist das die richtige Wahl. Mach aus dem Parameter eine Referenz und übergib &name:
fn greet(name: &str) { println!("Hello, {name}!");}
fn main() { let name = String::from("Ferris"); greet(&name); // nur ausleihen, nicht weggeben println!("Goodbye, {name}!"); // ✅ gehört immer noch uns}Ausgabe:
Hello, Ferris!Goodbye, Ferris!&String würde auch funktionieren, aber &str ist die übliche Wahl, weil es auch String-Literale annimmt. Siehe Borrowing und Strings. Wenn die Funktion den Wert ändern soll, nimm stattdessen &mut.
Lösung 2: Klonen
Abschnitt betitelt „Lösung 2: Klonen“Nimm diese Lösung, wenn der andere Code wirklich seine eigene Kopie braucht – zum Beispiel, um sie in einem Struct oder einem Vec zu speichern – und du das Original trotzdem noch brauchst. .clone() erzeugt eine vollständige, unabhängige Kopie:
struct Player { name: String, score: u32,}
fn main() { let name = String::from("Ferris"); let player = Player { name: name.clone(), score: 0 }; println!("{} startet mit {} Punkten", player.name, player.score); println!("Ursprünglicher Name: {name}");}Ausgabe:
Ferris startet mit 0 PunktenUrsprünglicher Name: FerrisSei ehrlich zu dir: Einen kurzen String oder einen kleinen Vec zu klonen ist billig und völlig in Ordnung, gerade wenn du noch lernst. Mach dir erst Gedanken, wenn du große Datenmengen in einer heißen Schleife klonst. Wenn Ausleihen (Lösung 1) funktioniert, ist das trotzdem die bessere Wahl.
Lösung 3: Copy für kleine Typen ableiten
Abschnitt betitelt „Lösung 3: Copy für kleine Typen ableiten“Nimm diese Lösung für kleine Structs, die nur aus einfachen Werten bestehen, etwa Koordinaten, Farben oder IDs. Schreib #[derive(Clone, Copy)] darüber, dann wird das Struct kopiert statt verschoben:
#[derive(Debug, Clone, Copy)]struct Point { x: i32, y: i32,}
fn show(p: Point) { println!("({}, {})", p.x, p.y);}
fn main() { let start = Point { x: 0, y: 3 }; show(start); // kopiert start show(start); // ✅ klappt auch ein zweites Mal println!("{:?}", start);}Ausgabe:
(0, 3)(0, 3)Point { x: 0, y: 3 }Copy funktioniert nur, wenn jedes Feld ebenfalls Copy ist. Ein Struct mit einem String- oder Vec-Feld kann nicht Copy sein. Nimm dafür Clone und ruf .clone() auf. Mehr dazu unter Wichtige Traits.
Lösung 4: Ownership zurückgeben
Abschnitt betitelt „Lösung 4: Ownership zurückgeben“Nimm diese Lösung, wenn eine Funktion einen Wert übernimmt, ihn ändert und du das Ergebnis haben willst. Die Funktion gibt den Wert zurück, und du speicherst ihn wieder:
fn add_signature(mut letter: String) -> String { letter.push_str("\n-- Ferris"); letter // zurückgeben}
fn main() { let letter = String::from("Liebe Rustaceans,"); let letter = add_signature(letter); // wieder übernehmen println!("{letter}");}Ausgabe:
Liebe Rustaceans,-- FerrisDas passt gut zu Funktionen im „Builder“-Stil. Für eine einfache Änderung an Ort und Stelle ist fn add_signature(letter: &mut String) oft leichter zu lesen.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“Eine for-Schleife über einen Vec. for task in tasks verschiebt den ganzen Vektor in die Schleife. Danach kannst du tasks nicht mehr benutzen:
for task in tasks { println!("Doing: {task}");}println!("{} tasks done", tasks.len()); // ❌ `tasks` moved due to this implicit call to `.into_iter()`Die Meldung sagt: tasks wurde durch den unsichtbaren Aufruf von .into_iter() verschoben. Lösung: Lauf über eine Ausleihe, for task in &tasks. Siehe For in Rust und Iteratoren.
Einen Wert in einen Vec oder ein Struct stecken. names.push(name) und Order { customer } verschieben den Wert beide nach innen. Benutzt du name oder customer danach noch, gibt es den Fehler. Lösung: Lies den Wert an seinem neuen Platz (names[0], order.customer), erledige deine anderen Aufgaben vor dem Verschieben, oder füge mit push einen .clone() hinzu.
Verschieben in einer Schleife. Steht log.push(message) in einer Schleife, verschiebt der erste Durchlauf message, und beim zweiten ist nichts mehr da. Der Compiler sagt dann value moved here, in previous iteration of loop („Wert wurde hier verschoben, im vorherigen Schleifendurchlauf“). Lösung: Füge message.clone() hinzu, oder erzeuge den Wert innerhalb der Schleife, damit jeder Durchlauf einen frischen bekommt.
Threads und move-Closures. thread::spawn(move || ...) verschiebt jede eingefangene Variable in den neuen Thread. Lösung: Klone die Daten vor dem Start des Threads, oder teile sie mit Arc. Siehe Nebenläufigkeit und Geteilter Zustand.
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- E0502: Du hast einen Wert ausgeliehen und wolltest ihn ändern, während die Ausleihe noch benutzt wurde.
- E0499: zwei veränderbare Ausleihen desselben Werts zur gleichen Zeit.
- E0106: eine fehlende Lifetime-Angabe, oft wenn du eine Referenz zurückgibst, um einen Move zu vermeiden.
- E0308: unpassende Typen, zum Beispiel ein
String, wo nach dem Umstieg auf eine Ausleihe&strerwartet wird. - Alle Compilerfehler: die vollständige Liste der Fehlerseiten.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Du rufst save(report) auf, wobei report ein String ist, und gibst danach report aus. Warum bekommst du E0382?
2. Eine Funktion muss einen Vec<String> nur lesen. Was ist meistens die beste Lösung?
3. Welches Struct kann #[derive(Clone, Copy)] benutzen?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0382 – Rust error codes index (auf Englisch): die offizielle Erklärung dieses Fehlers, mit weiteren Beispielen.
- Was ist Eigentümerschaft? – Das Rust-Buch: das Kapitel, das Moves,
CloneundCopySchritt für Schritt erklärt. - Referenzen und Borrowing – Das Rust-Buch: wie du mit
&einen Wert benutzt, ohne ihn zu verschieben. - Ownership and moves – Rust by Example (auf Englisch): kurze, ausführbare Beispiele für Moves und teilweise Moves.
Copy– Doku der Standardbibliothek (auf Englisch): welche TypenCopysind und was der Unterschied zwischenCopyundCloneist.