El concepto 'static en Rust es frecuentemente malinterpretado debido a su naturaleza dual. No es simplemente una marca de tiempo, sino que actúa de dos formas distintas dependiendo de su contexto: como un lifetime de referencia y como una restricción de tipo (trait bound).
Cuando vemos &'static T, nos referimos a una referencia que apunta a datos que viven durante toda la ejecución del programa. Esto ocurre típicamente con literales de cadena (&'static str), ya que estos valores se embeben directamente en el binario (en el segmento de datos de solo lectura) y, por tanto, su existencia es garantizada.
Sin embargo, cuando vemos T: 'static en una definición de función o estructura, el significado cambia radicalmente. Aquí, 'static no significa que el valor T viva para siempre, sino que el tipo T es “seguro” de guardar indefinidamente. Para que un tipo cumpla con el bound T: 'static, no debe contener ninguna referencia que tenga un lifetime más corto que 'static. Si un tipo es dueño de sus datos (como i32 o String), cumple con T: 'static. Si un tipo contiene una referencia (como struct S<'a>(&'a i32)), entonces S solo cumplirá con T: 'static si el lifetime 'a es también 'static. Esta distinción es crucial para usar tipos en hilos (std::thread::spawn) o en estructuras globales, donde Rust debe garantizar que los datos no queden colgando si el hilo sobrevive al ámbito original.
Por último, existen las relaciones de “outlives” (sobrevivencia). La notación 'a: 'b establece que el lifetime 'a dura al menos tanto como 'b. Por su parte, la notación T: 'a indica que todos los lifetimes contenidos dentro de T son, como mínimo, tan largos como 'a. Esto permite definir jerarquías de dependencia entre datos en estructuras complejas y traits.
use std::fmt::Display;
// Contenedor donde T debe ser seguro de guardar indefinidamente.
// T: 'static no significa que T viva siempre, sino que no tiene referencias locales.
struct Contenedor<T: 'static> {
valor: T,
}
// El trait define que 'a es el lifetime de los datos que procesará.
trait Procesador<'a> {
fn procesar(&self, dato: &'a str);
}
// Esta estructura usa un bound en el tipo T y un lifetime para una referencia.
struct Gestor<'a, T>
where
T: Display + 'static,
{
// T es 'static, por lo que es seguro mover Gestor a otros contextos.
dado: T,
// 'a debe durar al menos tanto como la vida de 'referencia.
referencia: &'a str,
}
impl<'a, T> Procesador<'a> for Gestor<'a, T>
where
T: Display + 'static,
{
fn procesar(&self, dato: &'a str) {
println!("Gestor (dado: {}, ref): '{}' y '{}'", self.dado, self.referencia, dato);
}
}
// El bound T: 'static es obligatorio para asegurar que el hilo no capture
// referencias locales que mueran antes de que el hilo termine.
fn spawn_thread<T: 'static + Send>(item: T) {
std::thread::spawn(move || {
println!("Hilo ejecutando valor: {}", item);
});
}
fn main() {
// 1. &'static str: Referencia a datos en el segmento de lectura del binario.
let literal: &'static str = "Eterno";
// 2. T: 'static: Un i32 es 'static porque es un tipo de valor (no tiene referencias).
let num: i32 = 100;
let cont = Contenedor { valor: num };
spawn_thread(cont.valor);
// 3. Outlives y lifetime bounds en un contexto de struct y trait.
let texto_temporal = String::from("Temporal");
let gestor = Gestor {
dado: 42,
referencia: &texto_temporal, // 'a es el lifetime de texto_temporal.
};
gestor.procesar(literal);
}
Explicación del Código
struct Contenedor<T: 'static>: Define un tipo genérico dondeTtiene la restricción de ser'static. Esto garantiza queContenedorno contenga referencias que mueran antes de que el contenedor sea destruido.trait Procesador<'a>: Establece un lifetime'avinculado al métodoprocesar. Cualquier implementación debe asegurar que los datos pasados aprocesarvivan al menos tanto como'a.struct Gestor<'a, T>: Utiliza una cláusulawherepara imponer dos condiciones:Tdebe implementarDisplayy ser'static, y el lifetime'ade la estructura debe ser compatible con la referencia almacenada enreferencia.impl<'a, T> Procesador<'a> for Gestor<'a, T>: Implementación del trait. El métodoprocesarrecibe un&'a str, lo que significa que el argumento debe tener un lifetime que sea, como mínimo, igual al lifetime'ade la estructura.fn spawn_thread<T: 'static + Send>(item: T): Esta función requiere queTsea'static. Esto es necesario porquestd::thread::spawntiene una firma que exige que el cierre (closure) y todo lo que capture sea'static, impidiendo que se capturen variables locales por referencia que podrían desaparecer mientras el hilo aún se ejecuta.let literal: &'static str: Enmain, declaramos una variable con el lifetime'staticexplícito, indicando que el string reside en el binario.let cont = Contenedor { valor: num }: Creamos unContenedorcon uni32. Comoi32es un tipo que no posee referencias, cumple con la restricciónT: 'static.gestor.procesar(literal): Llamamos al método pasandoliteral. El compilador verifica que el lifetime deliteral(que es'static) sea compatible con el lifetime'arequerido por el método de la implementación del trait.
N° 32