Weak în Rust

Referință slabă spre o valoare din Rc: nu o ține în viață; upgrade() dă Some(Rc) dacă valoarea mai există.

Sintaxă
use std::rc::{Rc, Weak};

fn downgrade(this: &Rc<T>) -> Weak<T>
fn upgrade(&self) -> Option<Rc<T>>

Ce face Weak

Rc::downgrade(&r) creează un Weak<T>, o referință „slabă”: nu mărește contorul de proprietari, deci nu împiedică eliberarea valorii. Pentru a folosi valoarea, apelezi upgrade(), care returnează Some(Rc) dacă valoarea încă există sau None dacă a fost eliberată.

Weak rezolvă problema ciclurilor: într-un arbore, părintele ține copiii cu Rc, iar copiii țin părintele cu Weak. Așa, când dispare arborele, totul se eliberează. Există și std::sync::Weak, perechea lui Arc, cu aceleași metode.

Ce returnează

downgrade: un Weak<T>. upgrade: Option<Rc<T>>.

Exemple

Exemplul 1: upgrade după ce valoarea a dispărut

use std::rc::Rc;

fn main() {
    let sesiune = Rc::new(String::from("sesiunea Anei"));
    let slaba = Rc::downgrade(&sesiune);

    println!("proprietari: {}, slabe: {}", Rc::strong_count(&sesiune), Rc::weak_count(&sesiune));
    println!("upgrade: {:?}", slaba.upgrade());

    drop(sesiune);
    println!("după drop, upgrade: {:?}", slaba.upgrade());
}

Rezultat

proprietari: 1, slabe: 1
upgrade: Some("sesiunea Anei")
după drop, upgrade: None

Exemplul 2: Arbore cu legătură spre părinte

Copilul își găsește părintele prin Weak. Contorul de proprietari al părintelui rămâne 1.

use std::cell::RefCell;
use std::rc::{Rc, Weak};

struct Nod {
    nume: String,
    parinte: RefCell<Weak<Nod>>,
    copii: RefCell<Vec<Rc<Nod>>>,
}

fn nod(nume: &str) -> Rc<Nod> {
    Rc::new(Nod {
        nume: nume.to_string(),
        parinte: RefCell::new(Weak::new()),
        copii: RefCell::new(Vec::new()),
    })
}

fn main() {
    let romania = nod("România");
    let iasi = nod("Iași");

    *iasi.parinte.borrow_mut() = Rc::downgrade(&romania);
    romania.copii.borrow_mut().push(Rc::clone(&iasi));

    match iasi.parinte.borrow().upgrade() {
        Some(p) => println!("{} este în {}", iasi.nume, p.nume),
        None => println!("{} nu are părinte", iasi.nume),
    }
    println!("{} are {} copii", romania.nume, romania.copii.borrow().len());
    println!("proprietari România: {}", Rc::strong_count(&romania));
}

Rezultat

Iași este în România
România are 1 copii
proprietari România: 1

Greșeli frecvente

  • Păstrezi rezultatul lui upgrade() mult timp: acel Rc ține valoarea în viață. Fă upgrade doar cât ai nevoie de valoare.
  • Presupui că upgrade() reușește mereu și scrii unwrap(): dacă proprietarii au dispărut, programul intră în panică.

Comenzi înrudite

  • Rc: Pointer cu numărarea referințelor: mai mulți proprietari împart aceeași valoare, eliberată când dispare ultimul.
  • RefCell: Permite modificarea unei valori prin referință comună (&), verificând regulile de împrumut la rulare, nu la compilare.
  • Arc: Varianta lui Rc care poate fi împărțită între fire: contorul de proprietari este actualizat atomic.
  • mem::drop: Distruge o valoare imediat, înainte de sfârșitul blocului: eliberează memoria, închide fișierul, deblochează mutex-ul.