Errore Rust E0507: cannot move out of a value behind a shared reference
E0507 significa che hai provato a prendere un valore (a spostarlo) da qualcosa che hai solo in prestito. Un valore preso in prestito lo puoi guardare, ma non portare via, perché il vero proprietario resterebbe con un buco. È un errore molto comune, soprattutto con Vec<String>, i campi delle struct e Option. La soluzione di solito è prendere in prestito invece di spostare, oppure usare .clone(). Questa pagina ti mostra prima le soluzioni e poi te le spiega. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0507]: cannot move out of `self.name` which is behind a shared reference --> src/main.rs:7:9 |7 | self.name // ❌ tries to give away a String that `self` only borrows | ^^^^^^^^^ 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() // ❌ tries to give away a String that `self` only borrows | ++++++++Il compilatore dice: «non puoi spostare fuori self.name, che sta dietro un riferimento condiviso». Questo è il programma che lo produce:
struct User { name: String,}
impl User { fn name(&self) -> String { self.name // ❌ tries to give away a String that `self` only borrows }}
fn main() { let u = User { name: String::from("Ferris") }; println!("{}", u.name());}Il commento dice: prova a cedere una String che self ha solo in prestito. Lo stesso codice di errore arriva con varie formulazioni. Significano tutte «questo l’hai solo in prestito, quindi non puoi spostarlo»:
cannot move out of index of `Vec<String>`perlet first = names[0];cannot move out of `*r` which is behind a shared referenceperlet owned = *r;dover: &Stringcannot move out of `nick` as enum variant `Some` which is behind a shared referencepermatch *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(sì, nemmeno&mutti permette di prenderlo e basta)cannot move out of an `Rc`perlet s: String = *shared;
Cosa significa
Sezione intitolata “Cosa significa”Guarda la riga 7. Il metodo restituisce String, un valore posseduto. Quindi self.name sposterebbe la stringa fuori dalla struct per consegnarla a chi chiama. Ma il metodo ha solo &self, un prestito condiviso (di sola lettura) dell’utente. Non possiede l’utente, quindi non può cederne il nome.
Conta anche la parte move occurs because ... does not implement the Copy trait (lo spostamento avviene perché il tipo non implementa Copy). Se il campo fosse un u32, Rust lo copierebbe in silenzio e non ci sarebbe nessun errore. Una String possiede memoria sull’heap, quindi si può solo spostare, non copiare.
Pensa di farti prestare la libreria di un amico per un pomeriggio. Puoi leggere qualsiasi libro sugli scaffali. Ma non puoi portarti a casa un libro: la libreria non è tua, e il tuo amico tornerebbe e troverebbe un buco.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”Ogni valore ha esattamente un proprietario, e il proprietario lo libera quando sparisce (vedi Ownership). Se potessi spostare self.name fuori da uno User preso in prestito, l’utente penserebbe ancora di possedere quella stringa. Più tardi l’utente viene eliminato e libererebbe la stringa una seconda volta, oppure qualcuno leggerebbe un campo che non c’è più. È un «use after free» o un «double free», due dei bug peggiori in C e C++.
Con un Vec è uguale. names[0] appartiene ancora al vettore. Se let first = names[0]; lo spostasse fuori, il vettore avrebbe un buco all’indice 0, e proverebbe a liberare di nuovo quella posizione quando viene eliminato. Per questo l’indicizzazione ti dà solo l’accesso, mai l’ownership (possesso).
La regola in una riga: attraverso un riferimento puoi leggere e (con &mut) cambiare, ma non puoi prendere. Se vuoi l’ownership, devi possedere il contenitore, oppure lasciare qualcos’altro al posto del valore.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: Prendi in prestito invece di spostare
Sezione intitolata “Soluzione 1: Prendi in prestito invece di spostare”Scegli questa quando ti serve solo leggere il valore. Nella maggior parte dei casi è la soluzione giusta. Restituisci &str invece di String, scrivi &v[0] invece di v[0], e prendi in prestito il contenuto di un Option con as_deref() o as_ref():
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 => "anonimo", }}
fn main() { let users = vec![ User { name: String::from("Ferris"), nickname: Some(String::from("granchio")) }, User { name: String::from("Corro"), nickname: None }, ]; let first = &users[0]; println!("Primo utente: {}", first.name()); for user in &users { println!("Mostrato come: {}", user.display_name()); } println!("Soprannome: {}", describe(&users[1].nickname));}Output:
Primo utente: FerrisMostrato come: granchioMostrato come: CorroSoprannome: anonimoGuarda describe: fa il match su nick (il riferimento), non su *nick. Così n è un &String, un prestito, e niente viene spostato. Scrivere match *nick { Some(n) => ... } chiede una String posseduta in n, ed è E0507. Lo dice anche il suggerimento del compilatore: consider removing the dereference here (valuta di togliere la dereferenziazione qui).
as_deref() trasforma un &Option<String> in un Option<&str>; as_ref() dà Option<&String>. Usali prima di .unwrap(), .map() o match, così lavori con un prestito invece che con il valore posseduto. Lo stesso vale per *r quando r: &String: di solito il * non serve proprio, usa semplicemente r. Vedi Option e Prestito (borrowing).
Soluzione 2: Clonalo
Sezione intitolata “Soluzione 2: Clonalo”Scegli questa quando ti serve davvero una copia tua, per esempio per salvarla altrove mentre l’originale resta dov’è. Il compilatore lo suggerisce in quasi tutti i messaggi E0507:
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} è il primo, la lista ha ancora {} nomi", names.len());
let u = User { name: String::from("Ferris") }; let mut badge = u.name(); badge.push_str(" (admin)"); println!("{badge} / {}", u.name);}Output:
Ferris è il primo, la lista ha ancora 2 nomiFerris (admin) / FerrisClonare un nome o una lista corta costa poco e va benissimo, soprattutto mentre stai imparando. Diventa un problema solo con grandi quantità di dati in un ciclo eseguito spesso. Se basta un prestito (Soluzione 1), preferiscilo.
Soluzione 3: Prendilo e lascia qualcosa al suo posto
Sezione intitolata “Soluzione 3: Prendilo e lascia qualcosa al suo posto”Scegli questa quando hai accesso &mut e vuoi spostare fuori davvero il valore, per esempio per consegnare un log finito o il prossimo compito. Non puoi lasciare un buco, ma puoi metterci un sostituto:
std::mem::take(&mut x)prende il valore e lascia il suo valore predefinito (""per unaString, vuoto per unVec).option.take()prende ciò che c’è dentro unOptione lasciaNone.std::mem::replace(&mut x, new)mette dentronewe ti restituisce il vecchio valore.
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("rilascio")), status: Status::Queued, }; let old = job.set_status(Status::Running); println!("Stato: {old:?} -> {:?}", job.status); println!("Prossimo passo: {:?}", job.start_next()); println!("Prossimo passo, di nuovo: {:?}", job.start_next()); println!("Log: {:?}", job.take_log()); println!("Log adesso: {:?}", job.log);}Output:
Stato: Queued -> RunningProssimo passo: Some("rilascio")Prossimo passo, di nuovo: NoneLog: "build ok"Log adesso: ""La struct non resta mai con un buco: dopo take_log il log è una stringa vuota, dopo start_next il passo è None. È la soluzione standard per «cannot move out of self.x which is behind a mutable reference». mem::take richiede che il tipo implementi Default; per gli altri tipi, usa mem::replace.
Soluzione 4: Possiedi la collezione, poi sposta fuori
Sezione intitolata “Soluzione 4: Possiedi la collezione, poi sposta fuori”Scegli questa quando hai finito con la collezione o con la struct e vuoi il suo contenuto. Se il Vec è tuo, può consegnarti gli elementi: remove(i), swap_remove(i), pop(), oppure into_iter() per tutti. Un metodo che prende self (non &self) possiede la struct e può spostarne fuori i campi:
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!("Mi occupo di {first}, restano: {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:?}");}Output:
Mi occupo di email, restano: ["backup", "report"]["Ferris", "Corro"]into_iter() consuma il vettore, quindi dopo non puoi più usare users (sarebbe E0382). Il prefisso into_ di into_name è un’abitudine di Rust per i nomi: dice a chi legge «questo consuma il valore». remove(0) fa scalare tutti gli altri elementi; se l’ordine non conta, swap_remove è più veloce. Vedi Togliere elementi.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”Raccogliere campi in un ciclo for su &users. for u in &users { names.push(u.name); } fallisce con cannot move out of `u.name` which is behind a shared reference. Soluzione: names.push(u.name.clone()), oppure cicla con for u in users se dopo non ti serve più users, oppure raccogli dei prestiti: users.iter().map(|u| u.name.as_str()).collect::<Vec<&str>>().
Chiamare .unwrap() su un campo Option attraverso un riferimento. config.title.unwrap() con config: &Config fallisce, perché unwrap prende l’Option per valore. Il compilatore suggerisce la soluzione: config.title.as_ref().unwrap() (dà &String), oppure config.title.as_deref() per un Option<&str>.
Spostare una variabile catturata dentro una closure Fn. Una closure che può essere chiamata molte volte non può cedere ciò che ha catturato: cannot move out of `report`, a captured variable in an `Fn` closure. Soluzione: prendi in prestito dentro la closure (&report), clona al suo interno, oppure, se viene eseguita una volta sola, cambia il vincolo in FnOnce. Vedi Fn, FnMut e FnOnce.
Dereferenziare un Rc. let s: String = *shared; dà cannot move out of an `Rc`, perché altri proprietari potrebbero ancora usare il valore. Soluzione: prendilo in prestito (&*shared o semplicemente &shared), clonalo, oppure usa Rc::unwrap_or_clone(shared). Un Box è diverso: lo possiedi, quindi let s = *boxed; funziona. Vedi Smart pointer.
Errori correlati
Sezione intitolata “Errori correlati”- E0382: uso di un valore spostato. Potevi spostarlo, ma poi hai usato di nuovo il valore.
- E0594: non puoi assegnare attraverso un riferimento
&. La stessa regola del «l’hai solo in prestito», ma per scrivere invece che per spostare. - E0596: cannot borrow as mutable, per esempio quando usi
mem::take(&mut self.x)in un metodo che ha solo&self. - E0308: mismatched types,
expected String, found &String, che compare spesso subito dopo essere passati a un prestito. Restituisci&str, oppure chiama.clone(). - E0515: cannot return a reference to a local variable, quando una soluzione basata sul prestito punta a dati che stanno per essere eliminati.
- Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. let first = names[0]; con names: Vec<String> dà E0507. Se devi solo stampare first, di solito qual è la soluzione migliore?
2. Un metodo fn next(&mut self) -> Option<Task> deve consegnare self.pending (un Option<Task>). Cosa compila?
3. Perché let n = nums[0]; va bene con nums: Vec<i32>, ma non con un Vec<String>?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0507 — Rust error codes index (in inglese): la spiegazione ufficiale, compresa la soluzione con
mem::replace. - What Is Ownership? — The Rust Book (in inglese): spostamenti,
CloneeCopy, le idee dietro questo errore. - References and Borrowing — The Rust Book (in inglese): cosa puoi e non puoi fare attraverso
&e&mut. std::mem::take— standard library docs (in inglese): prendere un valore da un&mutlasciando il suo valore predefinito; il fratellomem::replaceti fa scegliere il sostituto.Option— standard library docs (in inglese):as_ref,as_derefetake, con esempi.