Error E0507 de Rust: cannot move out of a value behind a shared reference
E0507 significa que intentaste quedarte con un valor (moverlo) sacándolo de algo que solo tenías prestado. Puedes mirar un valor prestado, pero no llevártelo, porque entonces su verdadero dueño se quedaría con un hueco. Es un error muy común, sobre todo con Vec<String>, con campos de structs y con Option. La solución suele ser tomarlo prestado en lugar de moverlo, o usar .clone(). Esta página te muestra primero las soluciones y después las explica. Para los demás errores de esta serie, mira Errores del compilador de Rust.
El mensaje de error
Sección titulada «El mensaje de error»error[E0507]: cannot move out of `self.name` which is behind a shared reference --> src/main.rs:7:9 |7 | self.name // ❌ intenta regalar un String que `self` solo tiene prestado | ^^^^^^^^^ 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() // ❌ intenta regalar un String que `self` solo tiene prestado | ++++++++En español: “no se puede mover self.name hacia fuera, porque está detrás de una referencia compartida”. El help propone clonar el valor.
Este es el programa que lo produce:
struct User { name: String,}
impl User { fn name(&self) -> String { self.name // ❌ intenta regalar un String que `self` solo tiene prestado }}
fn main() { let u = User { name: String::from("Ferris") }; println!("{}", u.name());}El mismo código de error aparece con varias frases distintas. Todas significan “esto solo lo tienes prestado, así que no puedes moverlo”:
cannot move out of index of `Vec<String>`conlet first = names[0];cannot move out of `*r` which is behind a shared referenceconlet owned = *r;, donder: &Stringcannot move out of `nick` as enum variant `Some` which is behind a shared referenceconmatch *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í, ni siquiera&mutte deja simplemente llevártelo)cannot move out of an `Rc`conlet s: String = *shared;
Qué significa
Sección titulada «Qué significa»Fíjate en la línea 7. El método devuelve String, un valor propio (owned). Así que self.name sacaría el string del struct y se lo entregaría a quien llama. Pero el método solo tiene &self, un préstamo compartido (de solo lectura) del usuario. No es dueño del usuario, así que no puede regalar su nombre.
La parte move occurs because ... does not implement the Copy trait también importa: dice que habría un move porque String no implementa el trait Copy. Si el campo fuera un u32, Rust lo copiaría sin decir nada y no habría error. Un String tiene memoria en el heap, así que solo se puede mover, no copiar.
Piensa en una estantería que un amigo te presta por una tarde. Puedes leer cualquier libro que haya en ella. Pero no puedes llevarte un libro a casa: la estantería no es tuya, y tu amigo volvería y se encontraría un hueco vacío.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»Cada valor tiene exactamente un dueño, y el dueño lo limpia cuando desaparece (mira Ownership). Si pudieras sacar self.name de un User prestado, el usuario seguiría creyendo que es dueño de ese string. Más tarde el usuario se libera, y liberaría el string por segunda vez, o alguien leería un campo que ya no está. Eso es un “use after free” (uso después de liberar) o un “double free” (doble liberación), dos de los peores bugs de C y C++.
Con un Vec pasa lo mismo. names[0] sigue siendo propiedad del vector. Si let first = names[0]; lo sacara, el vector tendría un hueco en el índice 0, e intentaría liberar ese hueco otra vez cuando se libere. Por eso indexar solo te da acceso, nunca el ownership (la propiedad).
La regla en una línea: a través de una referencia puedes leer y (con &mut) cambiar, pero no llevarte nada. Si quieres el ownership, tienes que ser dueño del contenedor, o dejar algo en el lugar del valor.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Toma prestado en lugar de mover
Sección titulada «Solución 1: Toma prestado en lugar de mover»Elige esta cuando solo necesitas leer el valor. Es la solución correcta casi siempre. Devuelve &str en lugar de String, escribe &v[0] en lugar de v[0], y toma prestado el contenido de 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 => "anónimo", }}
fn main() { let users = vec![ User { name: String::from("Ferris"), nickname: Some(String::from("cangrejo")) }, User { name: String::from("Corro"), nickname: None }, ]; let first = &users[0]; println!("Primer usuario: {}", first.name()); for user in &users { println!("Se muestra como: {}", user.display_name()); } println!("Apodo: {}", describe(&users[1].nickname));}Salida:
Primer usuario: FerrisSe muestra como: cangrejoSe muestra como: CorroApodo: anónimoFíjate en describe: hace match sobre nick (la referencia), no sobre *nick. Así n es un &String, un préstamo, y nada se mueve. Escribir match *nick { Some(n) => ... } pide un String propio en n, y eso es E0507. La pista del compilador también lo dice: consider removing the dereference here (prueba a quitar la desreferencia de aquí).
as_deref() convierte un &Option<String> en un Option<&str>; as_ref() te da un Option<&String>. Úsalos antes de .unwrap(), .map() o match, para trabajar con un préstamo en lugar de con el valor propio. Lo mismo vale para *r cuando r: &String: normalmente no necesitas el * para nada, usa r directamente. Mira Option y Borrowing.
Solución 2: Clónalo
Sección titulada «Solución 2: Clónalo»Elige esta cuando de verdad necesitas tu propia copia, por ejemplo para guardarla en otro sitio mientras el original se queda donde está. El compilador lo sugiere en casi todos los mensajes de 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} va primero, la lista sigue teniendo {} nombres", names.len());
let u = User { name: String::from("Ferris") }; let mut badge = u.name(); badge.push_str(" (admin)"); println!("{badge} / {}", u.name);}Salida:
Ferris va primero, la lista sigue teniendo 2 nombresFerris (admin) / FerrisClonar un nombre o una lista corta es barato, y está perfectamente bien, sobre todo mientras aprendes. Solo es un problema con datos grandes dentro de un bucle que se ejecuta muchísimas veces. Si con un préstamo (Solución 1) te basta, mejor.
Solución 3: Sácalo y deja algo en su lugar
Sección titulada «Solución 3: Sácalo y deja algo en su lugar»Elige esta cuando tienes acceso &mut y quieres sacar el valor de verdad, por ejemplo para entregar un log terminado o la siguiente tarea. No puedes dejar un hueco, pero sí poner un sustituto:
std::mem::take(&mut x)se lleva el valor y deja su valor por defecto (""para unString, vacío para unVec).option.take()se lleva lo que hay dentro de unOptiony dejaNone.std::mem::replace(&mut x, new)ponenewen su lugar y te devuelve el valor antiguo.
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("compilación correcta"), next_step: Some(String::from("desplegar")), status: Status::Queued, }; let old = job.set_status(Status::Running); println!("Estado: {old:?} -> {:?}", job.status); println!("Siguiente paso: {:?}", job.start_next()); println!("Siguiente paso otra vez: {:?}", job.start_next()); println!("Log: {:?}", job.take_log()); println!("Log ahora: {:?}", job.log);}Salida:
Estado: Queued -> RunningSiguiente paso: Some("desplegar")Siguiente paso otra vez: NoneLog: "compilación correcta"Log ahora: ""El struct nunca se queda con un hueco: después de take_log, el log es un string vacío; después de start_next, el paso es None. Esta es la solución habitual para “cannot move out of self.x which is behind a mutable reference”. mem::take necesita que el tipo implemente Default; para otros tipos, usa mem::replace.
Solución 4: Sé dueño de la colección y saca de ella
Sección titulada «Solución 4: Sé dueño de la colección y saca de ella»Elige esta cuando ya has terminado con la colección o con el struct y quieres su contenido. Si tú eres dueño del Vec, este puede entregarte elementos: remove(i), swap_remove(i), pop(), o into_iter() para todos. Un método que recibe self (no &self) es dueño del struct y puede sacar sus campos:
struct User { name: String,}
impl User { fn into_name(self) -> String { self.name }}
fn main() { let mut queue = vec![String::from("correo"), String::from("copia"), String::from("informe")]; let first = queue.remove(0); println!("Haciendo {first}, quedan: {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:?}");}Salida:
Haciendo correo, quedan: ["copia", "informe"]["Ferris", "Corro"]into_iter() consume el vector, así que después no puedes usar users (eso sería E0382). El prefijo into_ de into_name es una costumbre de nombres en Rust: le dice a quien lee “esto gasta el valor”. remove(0) desplaza todos los demás elementos una posición; si el orden no importa, swap_remove es más rápido. Mira Quitar elementos.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Reunir campos en un bucle for sobre &users. for u in &users { names.push(u.name); } falla con cannot move out of `u.name` which is behind a shared reference. Solución: names.push(u.name.clone()), o recorre con for u in users si no necesitas users después, o reúne préstamos: users.iter().map(|u| u.name.as_str()).collect::<Vec<&str>>().
Llamar a .unwrap() sobre un campo Option a través de una referencia. config.title.unwrap(), con config: &Config, falla porque unwrap recibe el Option por valor. El compilador sugiere la solución: config.title.as_ref().unwrap() (te da un &String), o config.title.as_deref() para obtener un Option<&str>.
Mover una variable capturada dentro de una closure Fn. Una closure que se puede llamar muchas veces no puede regalar lo que capturó: cannot move out of `report`, a captured variable in an `Fn` closure. Solución: toma prestado dentro de la closure (&report), clona dentro de ella o, si solo se ejecuta una vez, cambia la restricción a FnOnce. Mira Fn, FnMut y FnOnce.
Desreferenciar un Rc. let s: String = *shared; da cannot move out of an `Rc`, porque otros dueños pueden seguir usando el valor. Solución: tómalo prestado (&*shared o simplemente &shared), clónalo o usa Rc::unwrap_or_clone(shared). Un Box es distinto: eres su dueño, así que let s = *boxed; funciona. Mira Punteros inteligentes.
Errores relacionados
Sección titulada «Errores relacionados»- E0382: uso de un valor movido. Sí podías moverlo, pero volviste a usar el valor después.
- E0594: no se puede asignar a través de una referencia
&. La misma regla de “esto solo lo tienes prestado”, pero para escribir en lugar de mover. - E0596: no se puede tomar prestado como mutable, por ejemplo cuando usas
mem::take(&mut self.x)en un método que solo tiene&self. - E0308: tipos que no coinciden,
expected String, found &String(se esperabaStringy se encontró&String), que suele aparecer justo después de cambiar a un préstamo. Devuelve&str, o llama a.clone(). - E0515: no se puede devolver una referencia a una variable local, cuando una solución basada en préstamos apunta a datos que están a punto de liberarse.
- Todos los errores del compilador: la lista completa de páginas de errores.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. let first = names[0]; con names: Vec<String> da E0507. Si solo vas a imprimir first, ¿cuál suele ser la mejor solución?
2. Un método fn next(&mut self) -> Option<Task> debe entregar self.pending (un Option<Task>). ¿Qué compila?
3. ¿Por qué let n = nums[0]; funciona con nums: Vec<i32>, pero no con un Vec<String>?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0507 — Rust error codes index (en inglés): la explicación oficial, incluida la solución con
mem::replace. - What Is Ownership? — El libro de Rust (en inglés): los moves,
CloneyCopy, las ideas que hay detrás de este error. También existe una traducción al español de la comunidad del libro. - References and Borrowing — El libro de Rust (en inglés): qué puedes hacer y qué no a través de
&y&mut. std::mem::take— documentación de la biblioteca estándar (en inglés): saca un valor de un&muty deja su valor por defecto en su lugar; su hermanamem::replacete deja elegir el sustituto.Option— documentación de la biblioteca estándar (en inglés):as_ref,as_derefytake, con ejemplos.