Rust-Fehler E0507: cannot move out of a value behind a shared reference
E0507 bedeutet: Du wolltest einen Wert aus etwas herausnehmen (verschieben), das du dir nur ausgeliehen hast. Einen ausgeliehenen Wert darfst du ansehen, aber nicht mitnehmen – sonst hätte der eigentliche Owner (Eigentümer) plötzlich ein Loch. Der Fehler ist sehr häufig, besonders bei Vec<String>, Feldern von Structs und Option. Meistens hilft es, stattdessen auszuleihen oder .clone() zu benutzen. Diese Seite zeigt dir zuerst die Lösungen und erklärt sie dann. Die anderen Fehler dieser Reihe findest du unter Rust-Compilerfehler.
Die Fehlermeldung
Abschnitt betitelt „Die Fehlermeldung“error[E0507]: cannot move out of `self.name` which is behind a shared reference --> src/main.rs:7:9 |7 | self.name // ❌ will einen String weggeben, den `self` nur ausgeliehen hat | ^^^^^^^^^ move occurs because `self.name` has type `String`, which does not implement the `Copy` trait |help: consider cloning the value if the performance cost is acceptable |7 | self.name.clone() // ❌ will einen String weggeben, den `self` nur ausgeliehen hat | ++++++++Auf Deutsch: „self.name kann nicht herausverschoben werden, weil es hinter einer geteilten Referenz liegt.“ Das help schlägt vor, den Wert zu klonen.
Dieses Programm erzeugt den Fehler:
struct User { name: String,}
impl User { fn name(&self) -> String { self.name // ❌ will einen String weggeben, den `self` nur ausgeliehen hat }}
fn main() { let u = User { name: String::from("Ferris") }; println!("{}", u.name());}Derselbe Fehlercode kommt in mehreren Formulierungen vor. Sie bedeuten alle „du hast das nur ausgeliehen, also darfst du es nicht verschieben“:
cannot move out of index of `Vec<String>`beilet first = names[0];cannot move out of `*r` which is behind a shared referencebeilet owned = *r;mitr: &Stringcannot move out of `nick` as enum variant `Some` which is behind a shared referencebeimatch *nick { Some(n) => n, ... }cannot move out of `report`, a captured variable in an `Fn` closurecannot move out of `self.log` which is behind a mutable reference(ja, selbst mit&mutdarfst du den Wert nicht einfach mitnehmen)cannot move out of an `Rc`beilet s: String = *shared;
Was sie bedeutet
Abschnitt betitelt „Was sie bedeutet“Sieh dir Zeile 7 an. Die Methode gibt einen String zurück, also einen Wert, der dem Aufrufer gehört. self.name würde den String deshalb aus dem Struct herausverschieben und dem Aufrufer übergeben. Die Methode hat aber nur &self, eine geteilte Ausleihe des Users, die nur zum Lesen da ist. Der User gehört ihr nicht, also kann sie seinen Namen auch nicht weggeben.
Auch der Teil move occurs because ... does not implement the Copy trait ist wichtig („der Move passiert, weil der Typ den Trait Copy nicht implementiert“). Wäre das Feld ein u32, würde Rust es still kopieren, und es gäbe keinen Fehler. Ein String besitzt Speicher auf dem Heap, deshalb kann er nur verschoben, nicht kopiert werden.
Stell dir vor, du leihst dir für einen Nachmittag das Bücherregal eines Freundes. Jedes Buch darin darfst du lesen. Aber du darfst keins mit nach Hause nehmen: Das Regal gehört dir nicht, und dein Freund käme zurück zu einer leeren Lücke.
Warum Rust das macht
Abschnitt betitelt „Warum Rust das macht“Jeder Wert hat genau einen Owner, und der Owner räumt ihn auf, wenn er verschwindet (siehe Ownership). Könntest du self.name aus einem ausgeliehenen User herausverschieben, würde der User immer noch glauben, dass ihm der String gehört. Wird der User später aufgeräumt, würde er den String ein zweites Mal freigeben, oder jemand würde ein Feld lesen, das gar nicht mehr da ist. Das ist ein „Use after Free“ oder ein „Double Free“ – zwei der schlimmsten Bugs in C und C++.
Bei einem Vec ist es genauso. names[0] gehört weiterhin dem Vektor. Würde let first = names[0]; den Wert herausverschieben, hätte der Vektor an Index 0 ein Loch, und beim Aufräumen würde er diesen Platz noch einmal freigeben. Indizieren gibt dir deshalb nur Zugriff, nie die Ownership.
Die Regel in einem Satz: Über eine Referenz darfst du lesen und (mit &mut) ändern, aber nichts wegnehmen. Wenn du die Ownership willst, muss dir der Container gehören, oder du musst etwas an die Stelle des Werts legen.
So behebst du den Fehler
Abschnitt betitelt „So behebst du den Fehler“Lösung 1: Ausleihen statt verschieben
Abschnitt betitelt „Lösung 1: Ausleihen statt verschieben“Nimm diese Lösung, wenn du den Wert nur lesen musst. In den meisten Fällen ist das die richtige Wahl. Gib &str statt String zurück, schreib &v[0] statt v[0], und leih dir den Inhalt einer Option mit as_deref() oder as_ref() aus:
struct User { name: String, nickname: Option<String>,}
impl User { fn name(&self) -> &str { &self.name }
fn display_name(&self) -> &str { match self.nickname.as_deref() { Some(nick) => nick, None => &self.name, } }}
fn describe(nick: &Option<String>) -> &str { match nick { Some(n) => n, None => "anonym", }}
fn main() { let users = vec![ User { name: String::from("Ferris"), nickname: Some(String::from("crab")) }, User { name: String::from("Corro"), nickname: None }, ]; let first = &users[0]; println!("Erster Benutzer: {}", first.name()); for user in &users { println!("Angezeigt als: {}", user.display_name()); } println!("Spitzname: {}", describe(&users[1].nickname));}Ausgabe:
Erster Benutzer: FerrisAngezeigt als: crabAngezeigt als: CorroSpitzname: anonymSieh dir describe an: Das match läuft über nick (die Referenz), nicht über *nick. Dann ist n ein &String, also eine Ausleihe, und nichts wird verschoben. Schreibst du match *nick { Some(n) => ... }, verlangst du einen eigenen String in n, und das ist E0507. Der Hinweis des Compilers sagt das auch: consider removing the dereference here („entferne hier die Dereferenzierung“).
as_deref() macht aus einer &Option<String> eine Option<&str>, as_ref() liefert Option<&String>. Benutze sie vor .unwrap(), .map() oder match, damit du mit einer Ausleihe arbeitest statt mit dem Wert selbst. Dasselbe gilt für *r mit r: &String: Den * brauchst du meistens gar nicht, nimm einfach r. Siehe Option in Rust und Borrowing.
Lösung 2: Klonen
Abschnitt betitelt „Lösung 2: Klonen“Nimm diese Lösung, wenn du wirklich deine eigene Kopie brauchst, zum Beispiel, um sie woanders zu speichern, während das Original bleibt, wo es ist. Der Compiler schlägt sie in fast jeder E0507-Meldung vor:
struct User { name: String,}
impl User { fn name(&self) -> String { self.name.clone() }}
fn main() { let names = vec![String::from("Ferris"), String::from("Corro")]; let first = names[0].clone(); println!("{first} ist zuerst dran, die Liste hat noch {} Namen", names.len());
let u = User { name: String::from("Ferris") }; let mut badge = u.name(); badge.push_str(" (admin)"); println!("{badge} / {}", u.name);}Ausgabe:
Ferris ist zuerst dran, die Liste hat noch 2 NamenFerris (admin) / FerrisEinen Namen oder eine kurze Liste zu klonen ist billig und völlig in Ordnung, gerade wenn du noch lernst. Zum Problem wird es erst bei großen Datenmengen in einer heißen Schleife. Wenn eine Ausleihe (Lösung 1) reicht, ist sie die bessere Wahl.
Lösung 3: Herausnehmen und etwas zurücklassen
Abschnitt betitelt „Lösung 3: Herausnehmen und etwas zurücklassen“Nimm diese Lösung, wenn du &mut-Zugriff hast und den Wert wirklich herausnehmen willst, zum Beispiel, um ein fertiges Log oder die nächste Aufgabe weiterzugeben. Ein Loch darfst du nicht hinterlassen, aber du kannst einen Ersatz hineinlegen:
std::mem::take(&mut x)nimmt den Wert und lässt seinen Standardwert zurück (""bei einemString, leer bei einemVec).option.take()nimmt den Inhalt einerOptionund lässtNonezurück.std::mem::replace(&mut x, new)legtnewhinein und gibt dir den alten Wert zurück.
use std::mem;
#[derive(Debug)]enum Status { Queued, Running,}
struct Job { log: String, next_step: Option<String>, status: Status,}
impl Job { fn take_log(&mut self) -> String { mem::take(&mut self.log) }
fn start_next(&mut self) -> Option<String> { self.next_step.take() }
fn set_status(&mut self, new: Status) -> Status { mem::replace(&mut self.status, new) }}
fn main() { let mut job = Job { log: String::from("build ok"), next_step: Some(String::from("deploy")), status: Status::Queued, }; let old = job.set_status(Status::Running); println!("Status: {old:?} -> {:?}", job.status); println!("Nächster Schritt: {:?}", job.start_next()); println!("Nächster Schritt noch mal: {:?}", job.start_next()); println!("Log: {:?}", job.take_log()); println!("Log jetzt: {:?}", job.log);}Ausgabe:
Status: Queued -> RunningNächster Schritt: Some("deploy")Nächster Schritt noch mal: NoneLog: "build ok"Log jetzt: ""Das Struct hat nie ein Loch: Nach take_log ist das Log ein leerer String, nach start_next ist der Schritt None. Das ist die übliche Lösung für „cannot move out of self.x which is behind a mutable reference“. mem::take setzt voraus, dass der Typ Default implementiert; bei anderen Typen nimmst du mem::replace.
Lösung 4: Die Sammlung besitzen und dann herausverschieben
Abschnitt betitelt „Lösung 4: Die Sammlung besitzen und dann herausverschieben“Nimm diese Lösung, wenn du mit der Sammlung oder dem Struct fertig bist und seinen Inhalt haben willst. Wenn dir der Vec gehört, kann er Elemente herausgeben: remove(i), swap_remove(i), pop() oder into_iter() für alle auf einmal. Eine Methode, die self nimmt (nicht &self), besitzt das Struct und darf seine Felder herausverschieben:
struct User { name: String,}
impl User { fn into_name(self) -> String { self.name }}
fn main() { let mut queue = vec![String::from("email"), String::from("backup"), String::from("report")]; let first = queue.remove(0); println!("Erledige {first}, übrig: {queue:?}");
let users = vec![User { name: String::from("Ferris") }, User { name: String::from("Corro") }]; let names: Vec<String> = users.into_iter().map(|u| u.into_name()).collect(); println!("{names:?}");}Ausgabe:
Erledige email, übrig: ["backup", "report"]["Ferris", "Corro"]into_iter() verbraucht den Vektor, du kannst users danach also nicht mehr benutzen (das wäre E0382). Das Präfix into_ in into_name ist eine Namenskonvention in Rust: Es sagt dem Leser „das hier verbraucht den Wert“. remove(0) schiebt alle anderen Elemente nach vorn; wenn die Reihenfolge egal ist, ist swap_remove schneller. Siehe Elemente entfernen.
Wo du ihn oft siehst
Abschnitt betitelt „Wo du ihn oft siehst“Felder in einer for-Schleife über &users einsammeln. for u in &users { names.push(u.name); } scheitert mit cannot move out of `u.name` which is behind a shared reference. Lösung: names.push(u.name.clone()), oder lauf mit for u in users, wenn du users danach nicht mehr brauchst, oder sammle Ausleihen: users.iter().map(|u| u.name.as_str()).collect::<Vec<&str>>().
.unwrap() auf einem Option-Feld über eine Referenz aufrufen. config.title.unwrap() mit config: &Config scheitert, weil unwrap die Option als Wert übernimmt. Der Compiler schlägt die Lösung vor: config.title.as_ref().unwrap() (liefert &String) oder config.title.as_deref() für eine Option<&str>.
Eine eingefangene Variable in einem Fn-Closure verschieben. Ein Closure, der vielleicht mehrmals aufgerufen wird, kann nicht weggeben, was er eingefangen hat: cannot move out of `report`, a captured variable in an `Fn` closure. Lösung: Leih im Closure aus (&report), klone darin, oder ändere den Trait-Bound auf FnOnce, wenn er nur einmal läuft. Siehe Fn, FnMut und FnOnce.
Ein Rc dereferenzieren. let s: String = *shared; ergibt cannot move out of an `Rc`, denn andere Owner benutzen den Wert vielleicht noch. Lösung: Leih ihn aus (&*shared oder einfach &shared), klone ihn, oder benutze Rc::unwrap_or_clone(shared). Eine Box ist anders: Sie gehört dir, also funktioniert let s = *boxed;. Siehe Smart Pointer.
Verwandte Fehler
Abschnitt betitelt „Verwandte Fehler“- E0382: Benutzung eines verschobenen Werts. Du durftest verschieben, hast den Wert danach aber noch einmal benutzt.
- E0594: Zuweisung über eine
&-Referenz nicht möglich. Dieselbe Regel „du hast das nur ausgeliehen“, aber fürs Schreiben statt fürs Verschieben. - E0596: kann nicht veränderbar ausgeliehen werden, zum Beispiel wenn
mem::take(&mut self.x)in einer Methode steht, die nur&selfhat. - E0308: unpassende Typen,
expected String, found &String, oft direkt nach dem Umstieg auf eine Ausleihe. Gib&strzurück oder ruf.clone()auf. - E0515: Eine Referenz auf eine lokale Variable kann nicht zurückgegeben werden – wenn eine Lösung per Ausleihe auf Daten zeigt, die gleich aufgeräumt werden.
- Alle Compilerfehler: die vollständige Liste der Fehlerseiten.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. let first = names[0]; mit names: Vec<String> ergibt E0507. Was ist meistens die beste Lösung, wenn du first nur ausgibst?
2. Eine Methode fn next(&mut self) -> Option<Task> soll self.pending (eine Option<Task>) herausgeben. Was kompiliert?
3. Warum ist let n = nums[0]; bei nums: Vec<i32> in Ordnung, bei einem Vec<String> aber nicht?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- E0507 – Rust error codes index (auf Englisch): die offizielle Erklärung, einschließlich der Lösung mit
mem::replace. - Was ist Eigentümerschaft? – Das Rust-Buch: Moves,
CloneundCopy– die Ideen hinter diesem Fehler. - Referenzen und Borrowing – Das Rust-Buch: was du über
&und&mutdarfst und was nicht. std::mem::take– Doku der Standardbibliothek (auf Englisch): einen Wert aus einem&mutherausnehmen und seinen Standardwert zurücklassen; mit dem Geschwistermem::replacewählst du den Ersatz selbst.Option– Doku der Standardbibliothek (auf Englisch):as_ref,as_derefundtake, mit Beispielen.