Ir al contenido

Error E0106 de Rust: missing lifetime specifier (falta un lifetime)

El error E0106 significa: escribiste una referencia (&) en el tipo de retorno de una función o en un struct, y Rust no sabe de dónde viene ese préstamo. No es un fallo en tu lógica. El compilador solo necesita una palabra más de tu parte, o un pequeño cambio de plan. Casi todo el mundo se encuentra con este error en su primera semana con Rust, y la solución suele ser una sola línea. (Esta página es parte de Errores del compilador de Rust explicados.)

error[E0106]: missing lifetime specifier
--> src/main.rs:1:32
|
1 | fn longer(a: &str, b: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
|
1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
| ++++ ++ ++ ++

En español: “falta un especificador de lifetime”. La ayuda dice que el tipo de retorno contiene un valor prestado, pero la firma no indica si viene de a o de b, y propone añadir un lifetime con nombre.

El programa que lo provoca:

fn longer(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longer("apple", "fig"));
}

Una referencia es un préstamo: apunta a datos cuyo dueño es otro. Cada vez que una función devuelve una referencia, Rust necesita saber de quién son los datos a los que apunta, para comprobar que esos datos siguen vivos mientras quien llama usa el resultado.

Mira las tres marcas debajo de la línea 1. El compilador subraya a y b con ---- y el tipo de retorno con ^. Te está diciendo: “devuelves un &str prestado, y podría venir de a o de b. ¿De cuál?”. No va a adivinarlo.

Piensa en un libro prestado. Si un amigo te da un libro y te dice “es prestado”, quieres saber de quién, para saber cuándo hay que devolverlo. Un lifetime (tiempo de vida) es esa etiqueta pegada al préstamo.

Rust garantiza que una referencia nunca vive más que los datos a los que apunta (nada de “punteros colgantes”). Para comprobarlo en una llamada a función, el compilador solo mira la firma de la función, no su cuerpo. Así que la propia firma tiene que decir de dónde sale la referencia que se devuelve.

La mayoría de las veces nunca escribes lifetimes, porque Rust los rellena por ti con tres sencillas reglas de elisión:

  1. Cada parámetro que es una referencia recibe su propio lifetime.
  2. Si hay exactamente un parámetro que es referencia, la referencia devuelta toma prestado de él.
  3. Si es un método con &self o &mut self, la referencia devuelta toma prestado de self.

E0106 aparece cuando ninguna de estas reglas da una respuesta: dos o más parámetros que son referencias (y sin self), ningún parámetro que sea referencia, o una referencia guardada en un struct. La lección de Lifetimes lo explica a fondo, y Borrowing explica las referencias en sí.

Solución 1: Añade un lifetime con nombre ('a)

Sección titulada «Solución 1: Añade un lifetime con nombre ('a)»

Elige esta cuando la función de verdad devuelve un trozo de una de sus entradas. 'a es solo un nombre. Escribirlo en las entradas y en la salida dice “el resultado vive tanto como estas entradas”:

// 'a: el &str devuelto toma prestado de a o de b, así que vive tanto como ambos.
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("apple");
let second = String::from("fig");
println!("Palabra más larga: {}", longer(&first, &second));
}

Salida:

Palabra más larga: apple
▶ Pruébalo tú mismo

Si el resultado solo puede venir de un parámetro, pon 'a solo en ese parámetro: fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>. La sugerencia del compilador pone 'a en todo, lo cual funciona, pero es más estricto de lo necesario.

Elige esta cuando la función construye texto nuevo, o cuando simplemente no quieres que quien llama quede atado a un préstamo. Un String propio pertenece a quien llama, así que no hay nada que rastrear:

fn greeting(name: &str, lang: &str) -> String {
if lang == "es" {
format!("¡Hola, {name}!")
} else {
format!("Hello, {name}!")
}
}
fn main() {
println!("{}", greeting("Ana", "es"));
println!("{}", greeting("Sam", "en"));
}

Salida:

¡Hola, Ana!
Hello, Sam!
▶ Pruébalo tú mismo

Esto cuesta una pequeña reserva de memoria. Para la mayoría de los programas, eso está perfectamente bien. No te sientas mal por hacerlo.

Solución 3: Guarda datos propios en tu struct

Sección titulada «Solución 3: Guarda datos propios en tu struct»

Un campo de struct como title: &str da el mismo error:

error[E0106]: missing lifetime specifier
--> src/main.rs:2:12
|
2 | title: &str,
| ^ expected named lifetime parameter

En los structs, la mejor solución casi siempre es que el struct sea dueño de sus datos: usa String en lugar de &str, y Vec<T> en lugar de &[T]. Así el struct puede vivir todo lo que quieras:

#[derive(Debug)]
struct Book {
title: String, // propio, no &str
pages: u32,
}
fn main() {
let book = Book { title: String::from("Dune"), pages: 412 };
println!("{} ({} páginas)", book.title, book.pages);
}

Salida:

Dune (412 páginas)
▶ Pruébalo tú mismo

Dale un lifetime a un struct solo cuando sea una vista de corta duración sobre datos que viven en otro sitio, como un parser que mira un texto grande. Entonces escribes <'a> en el struct y en el campo:

struct Excerpt<'a> {
text: &'a str, // toma prestado de la novela de abajo
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first };
println!("Fragmento: {}", excerpt.text);
}

Salida:

Fragmento: Call me Ishmael
▶ Pruébalo tú mismo

Solución 4: Usa 'static para literales de cadena

Sección titulada «Solución 4: Usa 'static para literales de cadena»

Cuando una función no tiene ningún parámetro que sea referencia, el error dice “there is no value for it to be borrowed from” (no hay ningún valor del que pueda tomarse prestado):

error[E0106]: missing lifetime specifier
--> src/main.rs:1:22
|
1 | fn default_name() -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

Si solo devuelves literales de cadena como "OK", escribe &'static str. Los literales van incluidos dentro de tu programa, así que viven durante toda la ejecución:

fn status_text(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
_ => "Unknown",
}
}
fn main() {
println!("200 significa {}", status_text(200));
println!("404 significa {}", status_text(404));
}

Salida:

200 significa OK
404 significa Not Found
▶ Pruébalo tú mismo

Usa 'static solo para literales y constantes de verdad. Si el texto se construye mientras el programa se ejecuta (con format!, o leyéndolo de un archivo), 'static no te va a ayudar: devuelve un String (Solución 2).

  • Funciones auxiliares con dos entradas, como fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Solución: ata la salida solo a map con 'a (Solución 1).
  • Structs que guardan texto, como un User { name: &str } construido a partir de una entrada que analizaste. Solución: usa campos String (Solución 3).
  • Funciones que devuelven mensajes fijos, como fn error_label() -> &str. Solución: &'static str (Solución 4).

Los métodos son la buena noticia: fn name(&self) -> &str no necesita ningún lifetime, gracias a la regla 3.

Comprueba lo aprendido

1. ¿Por qué fn first_word(s: &str) -> &str compila sin ningún lifetime?

2. Tu struct Config { path: &str } da E0106. ¿Cuál suele ser la mejor solución?

3. Una función construye un mensaje con format! y lo devuelve. ¿Qué tipo de retorno debería tener?

Puntuación: 0 / 3