lifetimes (durate de viață, ‘a) în Rust

Adnotări ca 'a care spun compilatorului cât timp trebuie să rămână valide referințele, ca să nu existe niciodată o referință spre memorie eliberată.

Sintaxă
fn f<'a>(x: &'a str, y: &'a str) -> &'a str { ... }
struct Nume<'a> {
    camp: &'a str,
}
&'static str        // trăiește tot programul (literalii de text)

Ce face lifetimes (durate de viață, 'a)

Fiecare referință are o durată de viață (lifetime): porțiunea din program în care valoarea la care arată există. De cele mai multe ori compilatorul o deduce singur. Când nu poate, de obicei pentru o funcție care primește mai multe referințe și returnează una, îți cere s-o scrii. Eroarea tipică este E0106, missing lifetime specifier.

O adnotare 'a (apostrof + nume) nu schimbă cât trăiește o valoare. Ea doar descrie o legătură: fn mai_lung<'a>(a: &'a str, b: &'a str) -> &'a str înseamnă „rezultatul este valid cât timp sunt valide ambii parametri”. Cu această informație, compilatorul verifică la apelant că rezultatul nu este folosit după ce a dispărut unul dintre texte.

Regulile de deducere (elision) acoperă cazurile simple: dacă funcția are un singur parametru referință, rezultatul primește durata lui; la metode cu &self, rezultatul primește durata lui self. O structură care ține o referință trebuie să aibă parametrul de durată: struct Cuvant<'a> { text: &'a str }. 'static înseamnă „valid tot programul” – așa sunt literalii de text (&'static str).

Exemple

Exemplul 1: Eroarea E0106: compilatorul nu știe de unde vine rezultatul

Funcția returnează fie a, fie b. Fără adnotare, compilatorul nu poate verifica la apel cât timp rămâne valid rezultatul, deci cere un parametru de durată și chiar arată cum să-l scrii.

fn mai_lung(a: &str, b: &str) -> &str {
    if a.len() >= b.len() { a } else { b }
}

fn main() {
    println!("{}", mai_lung("Iași", "Cluj-Napoca"));
}

Mesajul compilatorului (programul nu se compilează)

error[E0106]: missing lifetime specifier
 --> main.rs:1:34
  |
1 | fn mai_lung(a: &str, b: &str) -> &str {
  |                ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
  |
1 | fn mai_lung<'a>(a: &'a str, b: &'a str) -> &'a str {
  |            ++++     ++          ++          ++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0106`.

Exemplul 2: Funcția corectată cu ‘a

Cu 'a, rezultatul este legat de ambii parametri. În main, ambele texte trăiesc până la sfârșit, deci folosirea rezultatului este sigură. Ultimul apel arată cazul cu un singur parametru: durata se deduce automat, fără adnotare.

fn mai_lung<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.chars().count() >= b.chars().count() { a } else { b }
}

fn primul_cuvant(text: &str) -> &str {
    text.split_whitespace().next().unwrap_or("")
}

fn main() {
    let oras1 = String::from("Iași");
    let oras2 = String::from("Cluj-Napoca");
    let rezultat = mai_lung(&oras1, &oras2);
    println!("Numele mai lung: {rezultat}");
    println!("Primul cuvânt: {}", primul_cuvant("Universitatea Alexandru Ioan Cuza"));
}

Rezultat

Numele mai lung: Cluj-Napoca
Primul cuvânt: Universitatea

Exemplul 3: O structură care ține o referință

Fragment nu copiază textul, doar îl împrumută; 'a spune că un Fragment nu poate trăi mai mult decât textul din care provine. Metoda primele returnează un &str cu aceeași durată.

struct Fragment<'a> {
    text: &'a str,
    sursa: &'static str,
}

impl<'a> Fragment<'a> {
    fn primele(&self, n: usize) -> &'a str {
        match self.text.char_indices().nth(n) {
            Some((poz, _)) => &self.text[..poz],
            None => self.text,
        }
    }
}

fn main() {
    let articol = String::from("Rust previne erorile de memorie la compilare.");
    let f = Fragment { text: &articol, sursa: "dexblog.ro" };
    println!("„{}...” (din {})", f.primele(12), f.sursa);
}

Rezultat

„Rust previne...” (din dexblog.ro)

Greșeli frecvente

  • Crezi că 'a prelungește viața unei valori. Nu face asta: adnotările doar descriu relații. Dacă valoarea dispare prea devreme, trebuie să schimbi codul (de exemplu să returnezi un String), nu adnotările.
  • Pui 'static peste tot ca să scapi de erori. &'static str acceptă doar texte care trăiesc tot programul (literali), nu un String citit de la tastatură.
  • Folosești referințe în structuri când nu este nevoie. O structură care deține datele (nume: String) nu are nevoie de lifetimes și este mult mai simplu de folosit; referințele în structuri merită doar când vrei să eviți copii mari.

Comenzi înrudite

  • borrowing și referințe (& și &mut): O referință (&) împrumută o valoare fără să o ia de la proprietar. Regula: oricâte referințe de citire SAU una singură de modificare.
  • ownership (proprietatea asupra valorilor): Regula de bază a lui Rust: fiecare valoare are un singur proprietar, iar când proprietarul dispare, valoarea este eliberată automat.
  • struct: Definește un tip nou care grupează mai multe valori cu nume (câmpuri), de exemplu un elev cu nume, clasă și medie.
  • generics și where: Scrii o funcție sau un tip o singură dată, pentru orice tip T; limitele (T: Trait) spun ce trebuie să știe să facă T.
  • const și static: const definește o valoare fixă calculată la compilare; static definește o variabilă globală cu o singură adresă, care trăiește tot programul.