Error E0382 de Rust: borrow of moved value (valor movido)
E0382 significa que entregaste un valor (lo moviste) y después intentaste usarlo otra vez. Es el error más común durante la primera semana con Rust, y casi siempre se arregla con una sola línea. Esta página te muestra primero la solución y después explica qué está pasando. 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[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); // aquí name se mueve dentro de greet | ---- value moved here8 | println!("Goodbye, {name}!"); // ❌ name ya no está | ^^^^ 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()); // aquí name se mueve dentro de greet | ++++++++“Borrow of moved value” quiere decir “préstamo de un valor movido”. La note sugiere que greet tome el valor prestado en lugar de quedárselo, y el help propone clonarlo.
Este es el programa que lo produce:
fn greet(name: String) { println!("Hello, {name}!");}
fn main() { let name = String::from("Ferris"); greet(name); // aquí name se mueve dentro de greet println!("Goodbye, {name}!"); // ❌ name ya no está}También puedes ver la frase “use of moved value” (uso de un valor movido). Es el mismo error. Rust dice “borrow” cuando la línea de después solo lee el valor (como println!) y “use” cuando intenta moverlo otra vez (como let other = name;).
Qué significa
Sección titulada «Qué significa»Lee las tres etiquetas de arriba abajo. Cuentan toda la historia:
- Línea 6:
namees unString, yStringno esCopy. Así que pasarlo a otro sitio lo mueve. - Línea 7:
greet(name)mueve el string dentro de la función. Ahoragreetes su dueña, y el string se libera cuandogreettermina. - Línea 8: intentas leer
nameotra vez, peromainya no es su dueño.
Piensa en darle a un amigo la única llave de tu casa. Una vez que se la has dado, ya no puedes abrir la puerta con ella, aunque recuerdes que antes la tenías.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»En Rust, cada valor tiene exactamente un dueño. Cuando el dueño desaparece, el valor se limpia. Así es como Rust libera la memoria sin recolector de basura. Las reglas del ownership (propiedad) se explican en la página de Ownership.
Si dos variables fueran dueñas del mismo String, las dos intentarían liberar la misma memoria al final. Eso es un “double free” (doble liberación), un bug real que hace fallar programas en C y C++. Rust lo evita haciendo que la asignación y las llamadas a funciones muevan el valor, de modo que siempre haya un único dueño.
Los tipos pequeños y simples, como i32, bool y char, son Copy: al pasarlos se hace una copia barata, y nunca verás E0382 con ellos. Los tipos que tienen memoria en el heap, como String, Vec y la mayoría de los structs, se mueven.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Tomar prestado con &
Sección titulada «Solución 1: Tomar prestado con &»Elige esta cuando la función solo necesita mirar el valor. Es la solución correcta casi siempre. Cambia el parámetro a una referencia y pasa &name:
fn greet(name: &str) { println!("Hello, {name}!");}
fn main() { let name = String::from("Ferris"); greet(&name); // lo prestamos, no lo regalamos println!("Goodbye, {name}!"); // ✅ sigue siendo nuestro}Salida:
Hello, Ferris!Goodbye, Ferris!&String también funcionaría, pero lo habitual es &str, porque acepta además literales de texto. Mira Borrowing y Strings. Si la función necesita cambiar el valor, usa &mut.
Solución 2: Clonarlo
Sección titulada «Solución 2: Clonarlo»Elige esta cuando el otro código de verdad necesita su propia copia, por ejemplo para guardarla en un struct o en un Vec, y tú sigues necesitando el original. .clone() hace una copia completa e independiente:
struct Player { name: String, score: u32,}
fn main() { let name = String::from("Ferris"); let player = Player { name: name.clone(), score: 0 }; println!("{} empieza con {} puntos", player.name, player.score); println!("Nombre original: {name}");}Salida:
Ferris empieza con 0 puntosNombre original: FerrisSé sincero contigo: clonar un string corto o un Vec pequeño es barato, y está perfectamente bien, sobre todo mientras aprendes. Solo preocúpate cuando clones datos grandes dentro de un bucle que se ejecuta muchísimas veces. Si un préstamo (Solución 1) funciona, mejor.
Solución 3: Derivar Copy en tipos pequeños
Sección titulada «Solución 3: Derivar Copy en tipos pequeños»Elige esta para structs pequeños hechos solo de valores simples, como coordenadas, colores o IDs. Añade #[derive(Clone, Copy)] y el struct se copiará en lugar de moverse:
#[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); // copia start show(start); // ✅ funciona otra vez println!("{:?}", start);}Salida:
(0, 3)(0, 3)Point { x: 0, y: 3 }Copy solo funciona si todos los campos también son Copy. Un struct con un campo String o Vec no puede ser Copy; para esos, usa Clone y llama a .clone(). Más sobre esto en Traits comunes.
Solución 4: Devolver el ownership
Sección titulada «Solución 4: Devolver el ownership»Elige esta cuando una función recibe un valor, lo cambia y quieres el resultado. La función devuelve el valor y tú lo vuelves a guardar:
fn add_signature(mut letter: String) -> String { letter.push_str("\n-- Ferris"); letter // lo devolvemos}
fn main() { let letter = String::from("Querida comunidad Rust:"); let letter = add_signature(letter); // lo recuperamos println!("{letter}");}Salida:
Querida comunidad Rust:-- FerrisEsto funciona bien para funciones de estilo “builder” (constructor). Para un cambio sencillo en el sitio, fn add_signature(letter: &mut String) suele ser más fácil de leer.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Un bucle for sobre un Vec. for task in tasks mueve el vector entero dentro del bucle, así que no puedes usar tasks después:
for task in tasks { println!("Doing: {task}");}println!("{} tasks done", tasks.len()); // ❌ `tasks` moved due to this implicit call to `.into_iter()`El mensaje dice que tasks se movió por la llamada implícita a .into_iter() que hace el bucle. Solución: recorre un préstamo, for task in &tasks. Mira Bucles for e Iteradores.
Meter un valor en un Vec o en un struct. names.push(name) y Order { customer } mueven el valor dentro. Usar name o customer después falla. Solución: léelo desde su nuevo sitio (names[0], order.customer), haz el resto del trabajo antes del move, o mete un .clone().
Mover dentro de un bucle. Si log.push(message) está dentro de un bucle, la primera vuelta mueve message y en la segunda ya no queda nada. El compilador dice value moved here, in previous iteration of loop (el valor se movió aquí, en la vuelta anterior del bucle). Solución: mete message.clone(), o crea el valor dentro del bucle para que cada vuelta tenga uno nuevo.
Hilos y closures move. thread::spawn(move || ...) mueve todas las variables capturadas al nuevo hilo. Solución: clona los datos antes de lanzar el hilo, o compártelos con Arc. Mira Concurrencia y Estado compartido.
Errores relacionados
Sección titulada «Errores relacionados»- E0502: tomaste prestado un valor y luego intentaste cambiarlo mientras el préstamo seguía en uso.
- E0499: dos préstamos mutables del mismo valor al mismo tiempo.
- E0106: falta un lifetime (missing lifetime specifier), algo habitual cuando devuelves una referencia para evitar un move.
- E0308: tipos que no coinciden (mismatched types), por ejemplo pasar un
Stringdonde se espera un&strdespués de cambiar a un préstamo. - 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. Llamas a save(report), donde report es un String, y después imprimes report. ¿Por qué aparece E0382?
2. Una función solo necesita leer un Vec<String>. ¿Cuál suele ser la mejor solución?
3. ¿Qué struct puede usar #[derive(Clone, Copy)]?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0382 — Rust error codes index (en inglés): la explicación oficial de este error, con más ejemplos.
- What Is Ownership? — El libro de Rust (en inglés): el capítulo que explica paso a paso los moves,
CloneyCopy. También existe una traducción al español de la comunidad del libro. - References and Borrowing — El libro de Rust (en inglés): cómo
&te deja usar un valor sin moverlo. - Ownership and moves — Rust by Example (en inglés): ejemplos cortos y ejecutables de moves y moves parciales.
- El trait
Copy— documentación de la biblioteca estándar (en inglés): qué tipos sonCopy, y la diferencia entreCopyyClone.