Error E0716 de Rust: temporary value dropped while borrowed (valor temporal)
E0716 significa: tomaste prestado un valor que no tiene nombre de variable y luego seguiste usando ese préstamo en una línea posterior. Rust tira esos valores sin nombre al final de la sentencia, así que el préstamo apuntaría a la nada. Asusta un poco, pero es uno de los errores de préstamo más fáciles de arreglar: normalmente basta con dividir una línea en dos. (Esta página forma parte de Errores del compilador de Rust explicados.)
El mensaje de error
Sección titulada «El mensaje de error»error[E0716]: temporary value dropped while borrowed --> src/main.rs:2:13 |2 | let s = String::from("hi").as_str(); // s toma prestado un String que no tiene nombre | ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use3 | println!("{s}"); // ❌ ese String ya no existe | - borrow later used here |help: consider using a `let` binding to create a longer lived value |2 ~ let binding = String::from("hi");3 ~ let s = binding.as_str(); // s toma prestado un String que no tiene nombre |El compilador te dice que un valor temporal se liberó mientras seguía prestado (temporary value dropped while borrowed) y te propone guardarlo con un let para que viva más. Este es el programa que lo provoca:
fn main() { let s = String::from("hi").as_str(); // s toma prestado un String que no tiene nombre println!("{s}"); // ❌ ese String ya no existe}Qué significa
Sección titulada «Qué significa»Un temporal es un valor que se crea en medio de una expresión pero nunca se guarda en una variable. En la línea de arriba, String::from("hi") construye un String de verdad, pero nadie es su dueño: no hay ningún let nombre = ... para él. Rust libera (hace drop, limpia) un temporal al final de la sentencia, es decir, en el ;.
Lee las tres etiquetas de izquierda a derecha:
^^^^debajo deString::from("hi"): aquí se crea unStringtemporal (creates a temporary value).-al final de la línea: aquí, en el;, eseStringse libera (is freed at the end of this statement).-debajo desen la línea 3: peros(un&strque apunta dentro de eseString) todavía se usa aquí (borrow later used here).
Piensa en un camarero que apunta tu pedido en una nota adhesiva y la tira en cuanto se aleja de la mesa. Si le dices a un amigo “el pedido está en esa nota”, tu amigo no encontrará nada. Tienes que quedarte tú con la nota, o apuntar el pedido en algún sitio que dure.
La parte help: del mensaje suele ser la respuesta: crea una variable (rustc la llama binding) para que el valor tenga un dueño que viva más.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»Es la misma regla que en E0597: una referencia nunca debe vivir más que el valor al que apunta, o se convierte en una referencia colgante (dangling reference) a memoria liberada. La única diferencia es que en E0597 el valor tiene nombre y muere en una }, mientras que en E0716 el valor no tiene nombre y muere en el ;.
Usar un temporal dentro de la misma sentencia está perfectamente bien. println!("{}", String::from("hi").as_str()); compila, porque el préstamo termina antes del ;. El error solo aparece cuando el préstamo se sigue necesitando en una línea posterior. Mira ¿Cuándo termina un préstamo? y Lifetimes.
Hay una excepción muy útil, llamada extensión del lifetime de los temporales (temporary lifetime extension). Si pones & (o &mut) directamente delante del temporal en un let, Rust mantiene vivo el temporal en silencio mientras viva la variable. Por eso estas líneas compilan:
struct Label<'a> { text: &'a str,}
fn make_title() -> String { String::from("Recibidos")}
fn main() { let greeting = &String::from("hi"); // ✅ extendido: & directamente en el let let queue = &mut vec![1, 2, 3]; // ✅ extendido queue.push(4); let label = Label { text: &make_title() }; // ✅ extendido: & directamente en un literal de struct println!("{greeting} {queue:?} {}", label.text);}Salida:
hi [1, 2, 3, 4] RecibidosLa extensión no atraviesa una llamada a un método ni a una función. String::from("hi").as_str(), make_title().as_str(), vec![1, 2].iter() y shout(&make_title()) toman prestado el temporal dentro de una llamada, así que el temporal sigue muriendo en el ; y obtienes E0716. No intentes memorizar todos los casos: si ves E0716, ponle nombre al valor.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Ponle nombre al temporal con let
Sección titulada «Solución 1: Ponle nombre al temporal con let»Elige esta casi siempre. Es lo que sugiere el compilador, y es la respuesta correcta para la mayoría de los errores E0716. Guarda primero el valor en su propia variable y luego toma prestado de esa variable en la línea siguiente.
Un caso muy común es dividir una línea de entrada. read_command() devuelve un String nuevo, .trim() y .split(' ') toman prestado de él, y el Vec<&str> se queda con esos préstamos:
let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716Rust señala read_command() y dice que se libera al final de la sentencia. Ponle nombre:
fn read_command() -> String { // En un programa real, esto leería una línea de stdin. String::from(" añadir leche huevos \n")}
fn main() { let line = read_command(); // ahora el String vive hasta el final de main let parts: Vec<&str> = line.trim().split(' ').collect(); println!("{parts:?}"); println!("comando: {}, elementos: {}", parts[0], parts.len() - 1);}Salida:
["añadir", "leche", "huevos"]comando: añadir, elementos: 2Te pasará exactamente lo mismo con entrada real: lines.next().unwrap().unwrap().trim().split(' ').collect() sobre io::stdin().lines() da E0716 por la misma razón. Pon primero let line = lines.next().unwrap().unwrap(); en su propia línea. Mira Strings.
Solución 2: Guarda valores propios en lugar de referencias
Sección titulada «Solución 2: Guarda valores propios en lugar de referencias»Elige esta cuando el préstamo tiene que vivir más que cualquier variable que puedas crear, por ejemplo en un struct que pasas de un lado a otro, o en un Vec que llenas dentro de un bucle. Un let dentro de un bucle se liberaría al final de cada vuelta, y eso solo convertiría E0716 en E0597.
Aquí tienes dos versiones que no compilan. Un struct que guarda un &str de un temporal, y un bucle que añade préstamos de strings nuevos:
let label = Label { text: make_title().as_str() }; // ❌ E0716
let mut names: Vec<&str> = Vec::new();for name in ["ferris", "corro"] { names.push(name.to_uppercase().as_str()); // ❌ E0716}La solución es que el struct y el Vec sean dueños de sus strings. Usa String en lugar de &str y quita el .as_str():
struct Label { text: String, // propio, así que no necesita lifetime}
fn make_title() -> String { String::from("Recibidos")}
fn main() { let label = Label { text: make_title() }; // el String se mueve dentro de Label println!("Etiqueta: {}", label.text);
let mut names: Vec<String> = Vec::new(); for name in ["ferris", "corro"] { names.push(name.to_uppercase()); // el Vec es dueño de cada String nuevo } println!("{names:?}");}Salida:
Etiqueta: Recibidos["FERRIS", "CORRO"]Cuando estás empezando, los campos propios (String, Vec<T>) son casi siempre la opción más fácil. Un struct con un campo &str necesita un lifetime y nunca puede vivir más que los datos que toma prestados. Mira Structs que guardan referencias y Structs.
Solución 3: Reorganiza la cadena
Sección titulada «Solución 3: Reorganiza la cadena»Elige esta cuando el temporal es el comienzo de una cadena de iteradores. Estas líneas crean un Vec con vec![...], lo toman prestado con .iter() y se guardan el iterador (o su resultado) para después:
let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716let best = vec![72, 95, 88].iter().max(); // ❌ E0716El &mut de delante no ayuda: la extensión del lifetime mantiene vivo el iterador, pero no el Vec del que toma prestado .iter(). Hay dos salidas:
- Usa
into_iter()para que el iterador sea dueño delVec(rustc también lo sugiere). Así tampoco necesitas&mut, sololet mut. - Termina la cadena con un resultado propio, como
.copied(),.cloned(),.sum()o.collect::<Vec<_>>()de números normales, para que nada tome prestado el temporal después del;.
fn main() { // El iterador es dueño del Vec, así que nada toma prestado un temporal let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10); println!("{:?}", doubled.next()); println!("{:?}", doubled.next());
// .copied() convierte Option<&i32> en Option<i32>, un valor sin préstamo let best = vec![72, 95, 88].iter().max().copied(); println!("Mejor: {best:?}");}Salida:
Some(10)Some(20)Mejor: Some(95)Si más tarde necesitas otra vez el Vec, vuelve a la Solución 1: let scores = vec![72, 95, 88]; y después scores.iter().max(). Mira Iteradores.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»Al bloquear un Mutex en medio de una cadena. let first = names.lock().unwrap().first(); da E0716, porque el guard del lock es un temporal. Cuando se libera, el lock se suelta, así que first no puede seguir apuntando dentro. Solución: let guard = names.lock().unwrap(); y luego guard.first(), o copia los datos de una vez con .first().cloned(). Mira Estado compartido.
Al pasar &temporal a una función que devuelve una referencia. Con fn shout(s: &str) -> &str, la línea let t = shout(&make_title()); da E0716: el &str devuelto toma prestado el String temporal, y la extensión del lifetime no atraviesa una llamada a función. Solución: let title = make_title(); y luego shout(&title).
Al devolverlo desde una función. Si escribes make_title().as_str() como última línea de una función que devuelve una referencia (como -> &'static str), obtienes E0515 (“cannot return value referencing temporary value”, no se puede devolver un valor que hace referencia a un valor temporal). Solución: devuelve el String.
Errores relacionados
Sección titulada «Errores relacionados»- E0597: borrowed value does not live long enough (el valor prestado no vive lo suficiente): el mismo problema para un valor que tiene nombre y se libera en una
}. - E0515: cannot return reference to local variable: devolver desde una función un préstamo de un valor local o temporal.
- E0106: missing lifetime specifier: un campo de struct como
text: &strnecesita un lifetime. A menudo es señal de queStringes la mejor opción. - E0502: mutable borrow while immutably borrowed: otro error de “un préstamo sigue en uso”, esta vez por cambiar los datos en lugar de liberarlos.
- E0382: borrow of moved value (uso de un valor movido): después de pasarte a valores propios, cuidado con usar un valor después de moverlo.
- Todos los errores del compilador explicados
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. En Rust, ¿qué es un "temporal"?
2. let s = String::from("hi").as_str(); seguido de println!("{s}"); da E0716. ¿Cuál es la solución habitual?
3. ¿Qué línea compila gracias a la extensión del lifetime de los temporales?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0716 — Rust error codes index (en inglés): la explicación oficial de este error, con la solución del
let. - References and Borrowing — El libro de Rust (en inglés): las reglas del borrowing, incluido por qué las referencias nunca pueden quedar colgando.
- Temporary scopes — The Rust Reference (en inglés): las reglas exactas de cuándo se libera un temporal.
- Temporary lifetime extension — The Rust Reference (en inglés): qué formas de
letmantienen vivo un temporal, con ejemplos. - Validating References with Lifetimes — El libro de Rust (en inglés): cómo compara Rust cuánto vive un valor con cuánto tiempo se usa una referencia.