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.)
El mensaje de error
Sección titulada «El mensaje de error»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"));}Qué significa
Sección titulada «Qué significa»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.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»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:
- Cada parámetro que es una referencia recibe su propio lifetime.
- Si hay exactamente un parámetro que es referencia, la referencia devuelta toma prestado de él.
- Si es un método con
&selfo&mut self, la referencia devuelta toma prestado deself.
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í.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»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: appleSi 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.
Solución 2: Devuelve un String propio
Sección titulada «Solución 2: Devuelve un String propio»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!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 parameterEn 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)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 IshmaelSolució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 fromSi 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 OK404 significa Not FoundUsa '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).
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»- Funciones auxiliares con dos entradas, como
fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Solución: ata la salida solo amapcon'a(Solución 1). - Structs que guardan texto, como un
User { name: &str }construido a partir de una entrada que analizaste. Solución: usa camposString(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.
Errores relacionados
Sección titulada «Errores relacionados»- E0382: borrow of moved value: otro error de ownership (propiedad), por usar un valor después de haberlo entregado.
- E0502: préstamo mutable mientras hay uno inmutable: el borrow checker trabajando una vez que tus lifetimes están en su sitio.
- E0499: dos préstamos mutables: dos
&muta los mismos datos a la vez. - E0308: mismatched types: por ejemplo, devolver un
Stringdesde una función que dice&str. - E0515: cannot return reference to local variable: intentaste la Solución 1 con datos creados dentro de la función. Devuelve un
Stringpropio (Solución 2). - Todos los errores del compilador explicados
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»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
Para leer más
Sección titulada «Para leer más»- E0106 — Rust error codes index (en inglés): la explicación oficial de este error.
- Validating References with Lifetimes — The Rust Book (en inglés): la historia completa de los lifetimes, con el ejemplo
longesty structs que guardan referencias. - Lifetime elision — The Rust Reference (en inglés): las reglas exactas que usa Rust para rellenar los lifetimes por ti.
- Elision — Rust by Example (en inglés): ejemplos cortos de cuándo puedes omitir los lifetimes.
- Lifetimes — Rust by Example (en inglés): ejemplos ejecutables de lifetimes explícitos en funciones, métodos y structs.