# Lifetimes en Rust Explicados: Anotaciones, Elisión y Preguntas de Entrevista 2026 > Dominar los lifetimes de Rust: anotaciones explícitas, reglas de elisión y preparación para entrevistas técnicas con ejemplos prácticos. - Published: 2026-08-27 - Updated: 2026-08-27 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Los lifetimes en Rust constituyen el mecanismo del compilador para rastrear cuánto tiempo permanecen válidas las referencias. A diferencia de los lenguajes con recolección de basura donde la gestión de memoria ocurre en tiempo de ejecución, el borrow checker de Rust valida la validez de las referencias en tiempo de compilación, eliminando categorías enteras de bugs de memoria antes de que el código se ejecute. > **Respuesta Rápida para Entrevistas** > > Un lifetime en Rust es una construcción en tiempo de compilación que describe el alcance durante el cual una referencia permanece válida. El borrow checker usa los lifetimes para garantizar que las referencias nunca sobrevivan a los datos que apuntan, previniendo punteros colgantes sin sobrecarga en tiempo de ejecución. ## Qué Representan Realmente los Lifetimes en Memoria Los lifetimes no se tratan de cuánto tiempo existen los valores. Describen cuánto tiempo las *referencias* a esos valores permanecen válidas. Cada referencia en Rust tiene un lifetime, incluso cuando las anotaciones se omiten. Consideremos esta función que el compilador rechaza: ```rust // dangling_reference.rs fn create_dangling() -> &String { let s = String::from("hello"); &s // ERROR: `s` se libera al final de la función } ``` El String `s` existe solo dentro del alcance de la función. Retornar una referencia a él crearía un puntero colgante, ya que la memoria del String se desaloca cuando la función retorna. El borrow checker detecta esto en tiempo de compilación. La solución requiere retornar un valor propio o asegurar que los datos referenciados sobrevivan a la llamada de función: ```rust // valid_return.rs // Opción 1: Retornar valor propio fn create_owned() -> String { String::from("hello") } // Opción 2: Referenciar datos que sobreviven a la función fn first_word(s: &str) -> &str { s.split_whitespace().next().unwrap_or("") } ``` En `first_word`, el `&str` retornado toma prestado de la entrada `s`, por lo que permanece válido mientras `s` lo esté. El llamador controla el lifetime de la entrada. ## Sintaxis y Semántica de las Anotaciones de Lifetime Las anotaciones de lifetime explícitas usan la sintaxis `'a`, `'b`, y así sucesivamente. Estas no son instrucciones al compilador sobre cuánto deben vivir las referencias. Describen relaciones entre los lifetimes de múltiples referencias. La [Referencia de Rust](https://doc.rust-lang.org/reference/trait-bounds.html#lifetime-bounds) define las anotaciones de lifetime como parámetros genéricos que restringen cuánto tiempo las referencias deben permanecer válidas entre sí. ```rust // lifetime_annotations.rs // Ambas entradas y la salida comparten el mismo lifetime 'a fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string"); let result; { let string2 = String::from("short"); result = longest(&string1, &string2); println!("El más largo: {}", result); // Válido: ambos strings existen } // println!("{}", result); // ERROR: string2 liberado } ``` La anotación `'a` le indica al compilador: la referencia retornada será válida por la *intersección* de los lifetimes de `x` e `y`. Como `string2` tiene un lifetime más corto, `result` no puede usarse después de que `string2` se libere. Múltiples lifetimes distintos expresan relaciones más complejas: ```rust // multiple_lifetimes.rs // La salida está ligada solo al lifetime del primer parámetro fn first_only<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str { x } fn main() { let owned = String::from("owned"); let result; { let temporary = String::from("temporary"); result = first_only(&owned, &temporary); } // result sigue válido: solo depende de owned println!("{}", result); } ``` ## Reglas de Elisión de Lifetimes en Rust 2024 El compilador aplica tres reglas de elisión para inferir lifetimes cuando las anotaciones se omiten. Estas reglas, documentadas en el [Rustonomicon](https://doc.rust-lang.org/nomicon/lifetime-elision.html), reducen el código repetitivo sin sacrificar la seguridad. > **Las Tres Reglas de Elisión** > > 1. Cada referencia de entrada obtiene su propio parámetro de lifetime > 2. Si existe exactamente un lifetime de entrada, se aplica a todas las referencias de salida > 3. Si existe `&self` o `&mut self`, su lifetime se aplica a todas las referencias de salida Estas reglas manejan la mayoría de los patrones comunes: ```rust // elision_examples.rs // Regla 1: Cada entrada obtiene su propio lifetime fn takes_two(x: &str, y: &str) {} // El compilador lee: fn takes_two<'a, 'b>(x: &'a str, y: &'b str) {} // Regla 2: Un solo lifetime de entrada se propaga a la salida fn first_char(s: &str) -> &str { &s[0..1] } // El compilador lee: fn first_char<'a>(s: &'a str) -> &'a str // Regla 3: El lifetime de &self se propaga a la salida impl Parser { fn peek(&self) -> &Token { &self.tokens[self.position] } // El compilador lee: fn peek<'a>(&'a self) -> &'a Token } ``` Cuando la elisión falla, el compilador requiere anotaciones explícitas. Esto ocurre con más frecuencia con funciones que retornan referencias derivadas de múltiples entradas: ```rust // elision_fails.rs // ERROR: No se puede determinar el lifetime de salida fn ambiguous(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } // CORRECCIÓN: La anotación explícita resuelve la ambigüedad fn unambiguous<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } ``` ## Lifetimes en Structs y la Relación Outlives Los structs que contienen referencias requieren anotaciones de lifetime para expresar que los datos prestados deben sobrevivir al struct: ```rust // struct_lifetimes.rs struct Excerpt<'a> { text: &'a str, } impl<'a> Excerpt<'a> { // new toma prestado de la entrada, por lo que Excerpt no puede sobrevivir a la fuente fn new(source: &'a str, start: usize, end: usize) -> Self { Excerpt { text: &source[start..end] } } // level() retorna datos propios, sin lifetime en la firma fn level(&self) -> u32 { self.text.len() as u32 / 10 } } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let excerpt = Excerpt::new(&novel, 0, 16); println!("Extracto: {}", excerpt.text); } // novel liberado, pero excerpt ya está fuera de alcance ``` El `'a` en `Excerpt<'a>` vincula la validez del struct al lifetime del `text` prestado. Intentar usar un `Excerpt` después de que su String fuente se libere genera un error de compilación. Para structs con múltiples referencias, cada una puede tener lifetimes distintos: ```rust // multiple_struct_lifetimes.rs struct Comparison<'a, 'b> { baseline: &'a str, candidate: &'b str, } impl<'a, 'b> Comparison<'a, 'b> { fn new(baseline: &'a str, candidate: &'b str) -> Self { Comparison { baseline, candidate } } fn baseline_only(&self) -> &'a str { self.baseline } } ``` ## El Lifetime 'static y Cuándo Usarlo El lifetime `'static` indica datos que viven durante toda la ejecución del programa. Los literales de cadena tienen este lifetime porque están incrustados en el binario: ```rust // static_lifetime.rs let s: &'static str = "Vivo para siempre"; // Las constantes son implícitamente 'static const CONFIG_VERSION: &str = "2.0.0"; // Los globales thread-safe requieren 'static static COUNTER: AtomicU64 = AtomicU64::new(0); ``` Un patrón común involucra las cotas `T: 'static`, lo cual confunde frecuentemente a los desarrolladores. Esta cota significa que `T` no contiene referencias no estáticas, no que `T` deba ser una referencia: ```rust // static_bound.rs use std::thread; fn spawn_task(data: T) { thread::spawn(move || { // data movida al thread, debe vivir independientemente println!("Procesando en el thread"); }); } fn main() { let owned = String::from("datos propios"); spawn_task(owned); // OK: String es 'static (posee sus datos) let reference = "prestada"; // spawn_task(reference); // OK: &'static str } ``` El thread podría sobrevivir a la función que lo creó, por lo que los datos movidos a threads no deben contener referencias a variables locales de la pila. ## Errores de Lifetime Comunes y Sus Soluciones Varios patrones hacen tropezar consistentemente a los desarrolladores. Comprender estos acelera la depuración. **Retornar referencias a variables locales:** ```rust // error_local_ref.rs fn bad_split(text: &str, delimiter: char) -> (&str, &str) { let parts: Vec<&str> = text.split(delimiter).collect(); (parts[0], parts[1]) // OK: parts[i] toma prestado de text, no del Vec } fn truly_bad() -> &str { let local = String::from("local"); &local // ERROR: local se libera al final de la función } ``` **Almacenar referencias en structs con lifetimes incompatibles:** ```rust // error_struct_lifetime.rs struct Cache { data: String, // view: &str, // ERROR: necesita parámetro de lifetime } // Los structs auto-referenciales requieren unsafe o crates como ouroboros struct CacheWithView<'a> { data: String, view: Option<&'a str>, // No puede apuntar a self.data de forma segura } ``` Los structs auto-referenciales, donde un campo referencia otro campo, requieren `Pin` con código unsafe o crates como [ouroboros](https://docs.rs/ouroboros/). El módulo [Rust smart pointers](/technologies/rust/interview-questions/smart-pointers) cubre estos patrones avanzados. **Cotas de lifetime en implementaciones de traits:** ```rust // trait_lifetime_bounds.rs trait Processor { fn process<'a>(&self, input: &'a str) -> &'a str; } struct Prefixer { prefix: String, } impl Processor for Prefixer { // No se puede retornar &format!(...) - sería temporal fn process<'a>(&self, input: &'a str) -> &'a str { input // Debe retornar input o parte de él } } ``` ## Higher-Ranked Trait Bounds (HRTBs) para Lifetimes Genéricos Los higher-ranked trait bounds usan la sintaxis `for<'a>` para expresar que un tipo debe satisfacer un trait para *cualquier* lifetime, no solo uno específico: ```rust // hrtb.rs use std::fmt::Debug; // F debe ser llamable con cualquier lifetime 'a fn apply_to_refs(f: F) where F: for<'a> Fn(&'a str) -> &'a str, { let owned = String::from("test"); let result = f(&owned); println!("{}", result); } fn identity(s: &str) -> &str { s } fn main() { apply_to_refs(identity); } ``` Los HRTBs aparecen frecuentemente en APIs que aceptan closures y el ecosistema [Rust async/await](/blog/rust/rust-async-await-tokio-futures-concurrency) donde los futures deben funcionar con referencias de lifetimes variados. ## Preguntas de Entrevista sobre Lifetimes en Rust Las entrevistas técnicas sondean la comprensión de lifetimes a múltiples niveles de profundidad. Estas preguntas aparecen regularmente en posiciones de Rust. **Pregunta: ¿Por qué este código falla al compilar?** ```rust // interview_q1.rs fn get_str() -> &str { "hello" } ``` **Respuesta:** El tipo de retorno necesita una anotación de lifetime explícita. Aunque el literal de cadena tiene lifetime `'static`, la firma de la función no lo expresa. La corrección: `fn get_str() -> &'static str`. **Pregunta: Explicar por qué esto compila y si es seguro:** ```rust // interview_q2.rs fn longest<'a>(x: &'a str, _y: &str) -> &'a str { x } ``` **Respuesta:** Esto compila porque el lifetime de salida está ligado solo a `x`. El parámetro `_y` puede tener cualquier lifetime ya que el valor de retorno no depende de él. Es seguro: la validez de la referencia retornada depende únicamente del lifetime de `x`. **Pregunta: ¿Qué sucede cuando se intenta almacenar una referencia junto a datos propios?** ```rust // interview_q3.rs struct Config<'a> { name: String, description: &'a str, } ``` **Respuesta:** Este patrón es válido pero restringe cómo se puede usar `Config`. El struct no puede sobrevivir a lo que `description` referencia. Para datos propios que necesitan referenciarse a sí mismos, considerar usar `String` para ambos campos, o las técnicas cubiertas en [Rust ownership y borrowing](/blog/rust/rust-ownership-borrowing-demystified). **Pregunta: ¿Cómo interactúan los lifetimes con los trait objects?** ```rust // interview_q4.rs trait Formatter { fn format(&self, input: &str) -> String; } fn get_formatter<'a>() -> Box { // ... } ``` **Respuesta:** Los trait objects tienen una cota de lifetime `'static` implícita por defecto. Escribir `Box` permite explícitamente que el trait object contenga referencias con lifetime `'a`. Sin la cota explícita, `Box` equivale a `Box`. ## Varianza de Lifetimes: Covarianza y Contravarianza La varianza determina cómo los lifetimes se relacionan cuando los tipos están anidados. Las referencias de Rust siguen estas reglas: - `&'a T` es **covariante** en `'a`: un lifetime más largo puede sustituir a uno más corto - `&'a mut T` es **invariante** en `T`: el tipo debe coincidir exactamente - `fn(&'a T)` es **contravariante** en `'a`: un lifetime más corto puede sustituir a uno más largo ```rust // variance.rs fn covariant_example() { let s: &'static str = "static"; let r: &str = s; // OK: 'static vive más que cualquier 'a } fn invariant_example() { let mut vec: Vec<&'static str> = vec!["a"]; // let s = String::from("local"); // vec.push(&s); // ERROR: &s no es &'static str } ``` Comprender la varianza importa al diseñar APIs genéricas que aceptan o retornan referencias. El artículo [Rust traits y generics](/blog/rust/rust-traits-generics-advanced-guide) cubre patrones genéricos avanzados. ## Anotaciones de Lifetime en la Práctica: Puntos Clave - Los lifetimes describen la validez de referencias, no la existencia de valores. El borrow checker los usa para prevenir punteros colgantes en tiempo de compilación. - Las reglas de elisión manejan la mayoría de los casos automáticamente. Las anotaciones explícitas se vuelven necesarias al retornar referencias derivadas de múltiples entradas. - Los lifetimes de struct expresan la relación outlives: cualquier struct que contenga referencias debe estar parametrizado por los lifetimes de esas referencias. - La cota `'static` en genéricos significa "no contiene referencias no estáticas", no "debe ser una referencia". - Los structs auto-referenciales requieren manejo especial a través de `Pin`, código unsafe, o crates auxiliares. - Las preguntas de entrevista se enfocan en comprender por qué el código falla al compilar y cómo las anotaciones de lifetime cambian las garantías de validez. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/rust/rust-lifetimes-explained-annotations-elision-interview