«Use of moved value» in Rust: Warum es passiert und wie man damit lebt
\n\nWenn Sie gerade erst anfangen, Rust zu lernen, sind Sie bestimmt schon auf die rätselhafte Meldung gestoßen: error[E0382]: use of moved value. Der Compiler schimpft, Sie kratzen sich am Kopf, und der Code, der logisch völlig korrekt ist, weigert sich zu kompilieren. Kommt Ihnen das bekannt vor? Willkommen in der Welt von Besitz und Ausleihe – den Schlüsselkonzepten von Rust, die es sicher machen, aber selbst erfahrene Entwickler manchmal vor ein Rätsel stellen.
Dieser Artikel ist keine trockene Dokumentation, sondern eine lebendige Analyse des Problems. Wir betrachten anhand von echten Codebeispielen, warum der Fehler use of moved value auftritt, und vor allem, was man dagegen tun kann. Sie werden lernen, wie der Borrow Checker denkt und werden aufhören, gegen ihn zu kämpfen. Los geht's!
\n\nWas ist ein «Move» in Rust und warum ist der Compiler so streng?
\n\nIn Rust hat jeder Wert genau einen Besitzer (owner). Wenn Sie einen Wert einer anderen Variablen zuweisen oder an eine Funktion übergeben, wird der Besitz verschoben (move). Nach der Verschiebung wird die ursprüngliche Variable ungültig – der Compiler verbietet den Zugriff auf sie. Das ist die Wurzel des Fehlers use of moved value.
\n\nSehen Sie sich dieses einfache Beispiel an:
\n\nlet s1 = String::from("hallo");\nlet s2 = s1; // Besitz wird von s1 zu s2 verschoben\nprintln!("{}", s1); // FEHLER: use of moved value: `s1`\n\nWarum ist das so? String ist keine einfache Kopie auf dem Stack. Sie speichert Daten auf dem Heap. Wenn Rust einfach den Zeiger kopieren würde, würden beim Freigeben des Speichers beide Variablen versuchen, ihn zu löschen – double free. Stattdessen «verschiebt» Rust den Besitz: Jetzt ist s2 für den Speicher verantwortlich, und s1 existiert aus Sicht des Compilers nicht mehr. Das ist die grundlegende Idee der Move-Semantik in Rust.
Typische Szenarien, die zu diesem Fehler führen
\n\nDer Fehler use of moved value tritt am häufigsten in drei Situationen auf:
- \n
- Übergabe eines Werts an eine Funktion \n
- Zuweisung eines Werts an eine andere Variable \n
- Verwendung eines Werts in einer Schleife oder einem Iterator \n
Betrachten wir jeden Fall im Detail.
\n\n1. Übergabe an eine Funktion ohne Rückgabe
\n\nfn print_string(s: String) {\n println!("{}", s);\n}\n\nfn main() {\n let my_string = String::from("Rust");\n print_string(my_string); // Besitz wurde verschoben\n println!("{}", my_string); // Fehler! use of moved value\n}\n\nNach dem Aufruf von print_string ist der Besitz von my_string an die Funktion übergegangen. Wenn die Funktion endet, wird s gelöscht, und my_string ist nicht mehr gültig. Der Compiler schützt Sie davor, ein totes Objekt zu verwenden.
2. Zuweisung ohne Kopie
\n\nDieses Beispiel mit String haben wir bereits gesehen. Aber wichtig: Nicht alle Typen verhalten sich so. Einfache Typen wie i32, bool, f64 implementieren das Copy-Trait. Sie werden nicht verschoben, sondern automatisch kopiert.
let x = 5;\nlet y = x; // Kopie, keine Verschiebung\nprintln!("{}", x); // funktioniert!\n\nTypen, die Daten auf dem Heap speichern (String, Vec, Box), implementieren Copy nicht – sondern nur Clone. Daher werden sie verschoben.
3. Iteration über eine Sammlung
\n\nEine weitere häufige Fehlerquelle sind for-Schleifen:
let v = vec![1, 2, 3];\nfor x in v { // v wird in den Iterator verschoben\n println!("{}", x);\n}\nprintln!("{:?}", v); // Fehler! use of moved value\n\nWenn Sie for x in v schreiben, rufen Sie tatsächlich into_iter() auf, das den Vektor konsumiert. Nach der Schleife existiert v nicht mehr.
Wie man es behebt: 4 bewährte Methoden
\n\nJetzt