Ir al contenido

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.)

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 use
3 | 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
}

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:

  1. ^^^^ debajo de String::from("hi"): aquí se crea un String temporal (creates a temporary value).
  2. - al final de la línea: aquí, en el ;, ese String se libera (is freed at the end of this statement).
  3. - debajo de s en la línea 3: pero s (un &str que apunta dentro de ese String) 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.

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] Recibidos
▶ Pruébalo tú mismo

La 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.

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(); // ❌ E0716

Rust 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: 2
▶ Pruébalo tú mismo

Te 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"]
▶ Pruébalo tú mismo

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.

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); // ❌ E0716
let best = vec![72, 95, 88].iter().max(); // ❌ E0716

El &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 del Vec (rustc también lo sugiere). Así tampoco necesitas &mut, solo let 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)
▶ Pruébalo tú mismo

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.

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.

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