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.

Lifetimes en Rust Explicados: Anotaciones, Elisión y Preguntas de Entrevista 2026

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:

dangling_reference.rsrust
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:

valid_return.rsrust
// 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 define las anotaciones de lifetime como parámetros genéricos que restringen cuánto tiempo las referencias deben permanecer válidas entre sí.

lifetime_annotations.rsrust
// 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:

multiple_lifetimes.rsrust
// 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, 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:

elision_examples.rsrust
// 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:

elision_fails.rsrust
// 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:

struct_lifetimes.rsrust
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:

multiple_struct_lifetimes.rsrust
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
    }
}

¿Listo para aprobar tus entrevistas de Rust?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

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:

static_lifetime.rsrust
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:

static_bound.rsrust
use std::thread;

fn spawn_task<T: Send + 'static>(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:

error_local_ref.rsrust
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:

error_struct_lifetime.rsrust
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. El módulo Rust smart pointers cubre estos patrones avanzados.

Cotas de lifetime en implementaciones de traits:

trait_lifetime_bounds.rsrust
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:

hrtb.rsrust
use std::fmt::Debug;

// F debe ser llamable con cualquier lifetime 'a
fn apply_to_refs<F>(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 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?

interview_q1.rsrust
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:

interview_q2.rsrust
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?

interview_q3.rsrust
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.

Pregunta: ¿Cómo interactúan los lifetimes con los trait objects?

interview_q4.rsrust
trait Formatter {
    fn format(&self, input: &str) -> String;
}

fn get_formatter<'a>() -> Box<dyn Formatter + 'a> {
    // ...
}

Respuesta: Los trait objects tienen una cota de lifetime 'static implícita por defecto. Escribir Box<dyn Formatter + 'a> permite explícitamente que el trait object contenga referencias con lifetime 'a. Sin la cota explícita, Box<dyn Formatter> equivale a Box<dyn Formatter + 'static>.

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
variance.rsrust
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 cubre patrones genéricos avanzados.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

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.
Reto diario

¿Sabrías detectar el bug en Rust?

Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador de SharpSkill

Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.

Actualizado el 27 de agosto de 2026

Compartir

Artículos relacionados