Cuando una estructura (struct) no posee la propiedad de los datos que almacena, sino que solo contiene referencias a ellos, se convierte en un “prestamista” (borrower). Esto introduce la necesidad de declarar lifetimes explícitos. Si un struct contiene una referencia y no se especifica su tiempo de vida, el compilador de Rust no puede garantizar que la referencia no se convierta en un puntero colgante (dangling pointer) cuando el objeto original sea liberado. Por ello, el struct debe estar parametrizado con un lifetime que indique: “esta estructura solo es válida mientras la referencia que contiene sea válida”.
En los métodos (impl), la complejidad aumenta. No solo debemos declarar los lifetimes en el bloque impl, sino que debemos decidir qué lifetime devuelve una función. Aquí entra en juego la elisión de lifetimes: Rust aplica reglas automáticas para simplificar el código. En un método, si la función devuelve una referencia, el compilador asume que el tiempo de vida del retorno está ligado al tiempo de vida de &self. Sin embargo, si necesitamos que una función devuelva una referencia que pertenece a un campo interno del struct (y no a la instancia del struct en sí misma), la elisión fallará y debemos ser explícitos para romper esa dependencia.
Finalmente, los enum con variantes que contienen referencias siguen las mismas reglas de los structs. Si una variante de un enum guarda una referencia, el enum completo debe parametrizarse con el lifetime correspondiente para asegurar la integridad de la memoria.
// Un struct que contiene dos referencias con lifetimes potencialmente distintos
struct Session<'a, 'b> {
data: &'a str,
metadata: &'b str,
}
impl<'a, 'b> Session<'a, 'b> {
// Constructor que asocia los lifetimes de los argumentos a la estructura
fn new(data: &'a str, metadata: &'b str) -> Self {
Self { data, metadata }
}
// Utiliza elision de lifetimes: el retorno está ligado al lifetime de &self
fn get_data(&self) -> &str {
self.data
}
// Requiere lifetime explícito: el retorno debe estar ligado al lifetime 'b
// de la referencia interna, no al lifetime de la llamada a &self
fn get_metadata_explicit(&self) -> &'b str {
self.metadata
}
}
fn main() {
let contenido_texto = String::from("Datos críticos");
let meta_texto = String::from("Versión 1.0");
// La instancia 'session' vive mientras vivan 'contenido_texto' y 'meta_texto'
let session = Session::new(&contenido_texto, &meta_texto);
// get_data usa elision, es seguro porque 'session' está viva
println!("Data: {}", session.get_data());
// get_metadata_explicit devuelve una referencia ligada a 'meta_texto'
println!("Meta: {}", session.get_metadata_explicit());
}
Análisis del Código
Session<'a, 'b>: Define una estructura con dos parámetros de lifetime independientes. Esto es crucial cuandodataymetadatapueden tener duraciones de vida diferentes en memoria.impl<'a, 'b> Session<'a, 'b>: El bloque de implementación debe declarar los lifetimes'ay'bpara que los métodos dentro puedan referenciarlos.Session::new(data: &'a str, metadata: &'b str): El constructor asigna los lifetimes de las referencias de entrada a los campos correspondientes delstruct.fn get_data(&self) -> &str: Aquí ocurre la elisión. Aunque no se escribe, el compilador interpreta el retorno como&'a_self str, donde'a_selfes el lifetime de la referencia aself. Es seguro porque el campodatavive tanto o más que el objetoSession.fn get_metadata_explicit(&self) -> &'b str: Este es el punto avanzado. Si usáramos la elisión aquí, el compilador pensaría que el retorno vive solo tanto como la llamada aget_metadata_explicit. Al escribir-> &'b str, le informamos que la referencia devuelta proviene directamente del campometadata, permitiendo que el valor sobreviva incluso después de que la instancia deSessionhaya dejado de existir (siempre quemetadatasiga vigente).session.get_metadata_explicit(): Gracias a la anotación explícita, el compilador permite extraer la referencia de metadatos con total seguridad de memoria.
N° 31