Los lifetimes garantizan que las referencias sean válidas durante todo el tiempo que se usan. El artículo introductorio explicó la sintaxis básica en funciones. Aquí vamos más allá: reglas de elisión que el compilador aplica automáticamente, anotaciones en structs e impl blocks, lifetime subtyping, el lifetime especial 'static y los Higher-Ranked Trait Bounds (for<'a>) para closures genéricos sobre lifetimes.
Reglas de elisión
El compilador aplica tres reglas automáticamente antes de pedir anotaciones explícitas:
- Cada parámetro de referencia recibe su propio lifetime.
- Si hay exactamente un parámetro de entrada con lifetime, ese lifetime se asigna a todos los de salida.
- Si hay un parámetro
&selfo&mut self, su lifetime se asigna a todos los de salida.
// Estas dos firmas son equivalentes gracias a la elisión:
fn primera_palabra(s: &str) -> &str { &s[..s.find(' ').unwrap_or(s.len())] }
fn primera_palabra_explicita<'a>(s: &'a str) -> &'a str { &s[..s.find(' ').unwrap_or(s.len())] }
// Aquí la elisión no puede resolver: hay dos parámetros, output ambiguo
// fn mas_largo(x: &str, y: &str) -> &str { ... } // ERROR
fn mas_largo<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Lifetimes en structs
// El struct no puede vivir más que la referencia que guarda
struct Fragmento<'a> {
parte: &'a str,
}
impl<'a> Fragmento<'a> {
fn nivel(&self) -> usize {
self.parte.len()
}
// La anotación en impl deja claro que el retorno vive tanto como self
fn anunciar_y_devolver<'b>(&'a self, aviso: &'b str) -> &'a str {
println!("Atención: {aviso}");
self.parte
}
}
fn main() {
let novela = String::from("Érase una vez...");
let primera;
{
let frag = Fragmento { parte: novela.split('.').next().unwrap() };
primera = frag.nivel();
}
println!("Longitud: {primera}");
}
Lifetime subtyping: 'a: 'b
La notación 'a: 'b significa que 'a vive al menos tanto como 'b. Útil cuando un lifetime debe sobrevivir a otro:
struct Cache<'a> {
datos: &'a str,
}
// 'long: 'short garantiza que datos vive mientras exista la caché
fn crear_cache<'long: 'short, 'short>(datos: &'long str) -> Cache<'short> {
Cache { datos }
}
fn main() {
let texto = String::from("datos importantes");
let cache = crear_cache(&texto);
println!("{}", cache.datos);
}
El lifetime 'static
'static indica que la referencia vive durante todo el programa. Aparece en literales de cadena y en tipos que exige thread::spawn:
use std::thread;
// Los literales &str tienen lifetime 'static
let s: &'static str = "siempre vivo";
// thread::spawn exige que el closure sea 'static (no capture referencias cortas)
fn lanzar_hilo(mensaje: String) {
thread::spawn(move || {
println!("{mensaje}"); // mensaje es propiedad del closure: 'static OK
});
}
// Truco para guardar datos estáticos
static CONFIGURACION: &str = "produccion";
fn main() {
println!("{CONFIGURACION}");
lanzar_hilo("hola".into());
}
Higher-Ranked Trait Bounds: for<'a>
Cuando un closure debe funcionar con referencias de cualquier lifetime (no uno específico), se usan HRTB:
// Esta función acepta un closure que trabaja con cualquier &str, sea cual sea su lifetime
fn aplicar_a_cadenas<F>(strings: &[&str], f: F) -> Vec<usize>
where
F: for<'a> Fn(&'a str) -> usize,
{
strings.iter().map(|s| f(s)).collect()
}
fn main() {
let palabras = vec!["hola", "mundo", "rust"];
let longitudes = aplicar_a_cadenas(&palabras, |s| s.len());
println!("{longitudes:?}"); // [4, 5, 4]
}
Error típico: struct que sobrevive a su referencia
struct Contenedor<'a> {
referencia: &'a String,
}
fn main() {
let c;
{
let s = String::from("temporal");
c = Contenedor { referencia: &s };
// `s` se destruye aquí
}
println!("{}", c.referencia); // ERROR
// error[E0597]: `s` does not live long enough
// Se resuelve ampliando el scope de `s` o usando String en lugar de &String
}
Resumen
- Las reglas de elisión cubren los casos más comunes; solo anotas explícitamente cuando son ambiguos.
- Los structs con referencias necesitan parámetros de lifetime: el struct no puede vivir más que la referencia.
'a: 'b(subtyping) expresa que un lifetime sobrevive a otro.'staticindica que la referencia dura todo el programa; es lo que exigethread::spawn.for<'a>(HRTB) permite closures genéricos sobre cualquier lifetime concreto.
