¿Por qué Rust obliga a indicar los tiempos de vida? ¿Y cómo simplifica la vida?
\n\nSi estás aprendiendo Rust, tarde o temprano te encuentras con esta misteriosa sintaxis: 'a, 'b... Y surge la pregunta lógica: «¿Por qué tengo que indicar explícitamente los tiempos de vida? ¡Otros lenguajes se las arreglan sin eso!»
Esta pregunta es una de las más populares en Stack Overflow (miles de visitas). Y no es de extrañar: el concepto de lifetimes en Rust parece complicado hasta que entiendes su esencia. Vamos a analizarlo sin teoría aburrida, con ejemplos prácticos.
\n\nRespuesta corta: seguridad sin recolector de basura
\n\nRust garantiza seguridad de memoria en tiempo de compilación. Necesita saber exactamente cuánto viven las referencias para evitar referencias colgantes (dangling references). En Java o Go, un recolector de basura se encarga de eso. En C++, tú eres responsable de la memoria (y a menudo cometes errores). Rust eligió un tercer camino: el compilador verifica todo estáticamente.
\n\nPero ¿por qué el compilador no puede deducirlo por sí mismo? Puede, en el 80% de los casos. Esto se llama lifetime elision (reglas de inferencia implícita). Sin embargo, en situaciones complejas necesita tu ayuda.
\n\nRegla n.º 1: El compilador no es adivino
\n\nObserva esta función:
\n\nfn longest(x: &str, y: &str) -> &str {\n if x.len() > y.len() { x } else { y }\n}\n\nA primera vista, todo tiene sentido. Pero Rust mostrará un error: «missing lifetime specifier». ¿Por qué?
\n\nImagina que pasas x — una referencia a una cadena que vive 5 segundos, y y — una referencia a una cadena que vive 1 segundo. La función puede devolver cualquiera de ellas. ¿Cuál será el tiempo de vida de la referencia devuelta? ¡El compilador no lo sabe!
La solución es indicar explícitamente: «la referencia devuelta vivirá tanto como ambas de entrada»:
\n\nfn longest<'a>(x: &'a str, y: &'a str) -> &'a str {\n if x.len() > y.len() { x } else { y }\n}\n\nEl símbolo 'a es una anotación de tiempo de vida. Decimos: «las tres referencias deben tener el mismo tiempo de vida mínimo». Ahora el compilador puede verificar que el código llamante no destruya los datos antes de tiempo.
Regla n.º 2: Referencias diferentes, vidas diferentes
\n\nUn error común de principiantes es pensar que todos los 'a deben ser iguales. En realidad, se pueden indicar diferentes tiempos de vida:
fn example<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {\n x // Ok, devolvemos una referencia con tiempo de vida 'a\n // y // ¡Error! y vive 'b, pero prometimos 'a\n}\n\nAquí decimos explícitamente: x e y pueden tener diferentes tiempos de vida. Pero solo podemos devolver la referencia cuyo tiempo de vida coincida con el prometido en el tipo de retorno.
Regla n.º 3: Las estructuras también pueden almacenar referencias
\n\nCuando creas una estructura que contiene referencias, los tiempos de vida en Rust deben indicarse obligatoriamente:
\n\nstruct Book<'a> {\n title: &'a str,\n author: &'a str,\n}\n\nfn main() {\n let title = String::from("Rust Book");\n let book;\n {\n let author = String::from("John Doe");\n book = Book { title: &title, author: &author };\n } // ¡author se destruye aquí!\n println!("{}", book.title); // ¡Error! book.author está colgando\n}\n\nEl compilador no permitirá compilar este código, porque book.author hace referencia a una cadena ya eliminada. Los lifetimes explícitos en Rust permiten detectar estos errores en tiempo de compilación, no en tiempo de ejecución.
Regla n.º 4: Lifetime Elision — cuándo no es necesario escribir
\n\nRust es lo suficientemente inteligente como para inferir los tiempos de vida en casos simples. Aquí es cuando no es necesario indicarlos:
\n\n- \n
- Una sola referencia de entrada — el tiempo de vida de salida es igual al tiempo de