Aprende Rust · Guía 4 de 8 · 3 min

Lifetimes: cuánto vive una referencia

Qué significa 'a, por qué casi nunca hay que escribirlo y cómo resolver los errores más comunes.

El problema que resuelven

Aquí r intenta seguir usándose cuando s ya no existe:

fn main() {
    let r;
    {
        let s = String::from("hola");
        r = &s;
    } // `s` se libera aquí
    println!("{r}"); // error[E0597]: `s` does not live long enough
}
s (el dato)r (referencia)s se libera aquíapunta a nada
El tiempo avanza hacia la derecha. El tramo rojo nunca llega a ejecutarse: Rust lo rechaza al compilar.

En C, un error así puede compilar y convertirse en un fallo de seguridad. En Rust, no pasa del compilador.

La buena noticia: casi nunca los escribes

Rust deduce la mayoría de los lifetimes por ti. Esta función no necesita ninguna anotación, porque solo hay una entrada posible para la salida:

fn primera_palabra(texto: &str) -> &str {
    texto.split(' ').next().unwrap_or("")
}

fn main() {
    println!("{}", primera_palabra("hola mundo"));
}

Cuándo sí hay que escribirlos

Con dos referencias de entrada, Rust no puede adivinar de cuál depende el resultado:

fn mas_largo(a: &str, b: &str) -> &str { // error[E0106]
    if a.len() > b.len() { a } else { b }
}

La solución es ponerle nombre a esa relación:

fn mas_largo<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() { a } else { b }
}

fn main() {
    let a = String::from("Arequipa");
    let b = String::from("Lima");
    println!("{}", mas_largo(&a, &b));
}

Se lee así: el resultado es válido mientras a y b sean válidos.

Lo que el compilador protege

Con esa firma, Rust detecta un uso peligroso, aunque aquí el texto más largo sea a. El error dice que b no vive lo suficiente:

fn mas_largo<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() { a } else { b }
}

fn main() {
    let a = String::from("Arequipa");
    let r;
    {
        let b = String::from("Lima");
        r = mas_largo(&a, &b); // error[E0597]
    }
    println!("{r}");
}

En structs

Si un struct guarda una referencia, debe declarar que no puede vivir más que ese dato:

struct Extracto<'a> {
    texto: &'a str,
}

fn main() {
    let libro = String::from("Había una vez. Fin.");
    let primera = libro.split('.').next().unwrap();
    let cita = Extracto { texto: primera };
    println!("{}", cita.texto);
}

'static: vive todo el programa

Los textos literales están dentro del ejecutable, así que su lifetime es 'static:

fn main() {
    let saludo: &'static str = "Hola";
    println!("{saludo}");
}

Si el compilador te sugiere 'static, desconfía: rara vez es la solución correcta para un dato que creaste en tiempo de ejecución.

Qué hacer cuando el compilador se queja

Prueba en este orden:

  1. Devuelve un valor propio. Un String en lugar de un &str elimina el problema.
  2. Declara el dato en un alcance más amplio. Así vive más que la referencia.
  3. Usa .clone(). Mientras aprendes, es una salida válida.

Construye mejor software.

Seguridad, eficiencia y oficio, con fuentes y en español.