Rc în Rust

Pointeri inteligenți: Box, Rc, Arc Rust 1.0 (strong_count: 1.15)

Pointer cu numărarea referințelor: mai mulți proprietari împart aceeași valoare, eliberată când dispare ultimul.

Sintaxă
use std::rc::Rc;

fn new(value: T) -> Rc<T>
fn clone(&self) -> Rc<T>
fn strong_count(this: &Rc<T>) -> usize

Ce face Rc

Rc<T> (reference counted) permite ca aceeași valoare să aibă mai mulți proprietari. Rc::new(x) o pune în heap cu un contor egal cu 1. Rc::clone(&r) nu copiază valoarea: creează încă un pointer spre ea și mărește contorul. Când un Rc dispare, contorul scade; la 0, valoarea este eliberată.

Rc::strong_count(&r) spune câți proprietari are valoarea acum. Prin convenție se scrie Rc::clone(&r), nu r.clone(), ca să fie clar că este o operație ieftină.

Valoarea dintr-un Rc poate fi doar citită. Pentru modificare, combină-l cu RefCell (Rc<RefCell<T>>). Rc nu poate trece între fire; acolo folosește Arc.

Parametri

Parametru Descriere
value Valoarea împărțită (la new).

Ce returnează

new și clone returnează un Rc<T>; strong_count returnează numărul de proprietari.

Exemple

Exemplul 1: Contorul crește și scade

Două familii folosesc aceeași adresă; în blocul interior apare un al treilea proprietar, care dispare la final de bloc.

use std::rc::Rc;

fn main() {
    let adresa = Rc::new(String::from("Str. Lăpușneanu 10, Iași"));
    println!("proprietari: {}", Rc::strong_count(&adresa));

    let familia_pop = Rc::clone(&adresa);
    println!("proprietari: {}", Rc::strong_count(&adresa));
    {
        let familia_ionescu = Rc::clone(&adresa);
        println!("în bloc: {} (Ionescu: {familia_ionescu})", Rc::strong_count(&adresa));
    }
    println!("după bloc: {}", Rc::strong_count(&adresa));
    println!("Pop: {familia_pop}");
}

Rezultat

proprietari: 1
proprietari: 2
în bloc: 3 (Ionescu: Str. Lăpușneanu 10, Iași)
după bloc: 2
Pop: Str. Lăpușneanu 10, Iași

Exemplul 2: Aceeași valoare în două liste

Produsul nu este copiat: ambele liste țin un Rc spre același obiect. Rc::ptr_eq confirmă că e aceeași adresă.

use std::rc::Rc;

#[derive(Debug)]
struct Produs {
    nume: String,
    pret: f64,
}

fn main() {
    let cafea = Rc::new(Produs { nume: String::from("cafea"), pret: 24.5 });

    let cos_ana = vec![Rc::clone(&cafea)];
    let cos_dan = vec![Rc::clone(&cafea), Rc::new(Produs { nume: String::from("ceai"), pret: 12.0 })];

    println!("Ana: {:?}", cos_ana);
    println!("Dan are {} produse, total {} lei", cos_dan.len(), cos_dan.iter().map(|p| p.pret).sum::<f64>());
    println!("același obiect: {}", Rc::ptr_eq(&cos_ana[0], &cos_dan[0]));
    println!("proprietari cafea: {}", Rc::strong_count(&cafea));
    println!("{}", cafea.nume);
}

Rezultat

Ana: [Produs { nume: "cafea", pret: 24.5 }]
Dan are 2 produse, total 36.5 lei
același obiect: true
proprietari cafea: 3
cafea

Greșeli frecvente

  • Încerci să modifici valoarea: Rc::new(vec![]).push(1) nu compilează (cannot borrow as mutable). Folosește Rc<RefCell<Vec<_>>>.
  • Creezi cicluri de Rc (A ține B și B ține A): contorul nu ajunge niciodată la 0 și memoria nu se eliberează. Pentru legăturile înapoi folosește Weak.
  • Trimiți un Rc într-un fir: eroarea Rc<...> cannot be sent between threads safely. Folosește Arc.

Comenzi înrudite

  • Weak: Referință slabă spre o valoare din Rc: nu o ține în viață; upgrade() dă Some(Rc) dacă valoarea mai există.
  • Arc: Varianta lui Rc care poate fi împărțită între fire: contorul de proprietari este actualizat atomic.
  • RefCell: Permite modificarea unei valori prin referință comună (&), verificând regulile de împrumut la rulare, nu la compilare.
  • Box: Pune o valoare în memoria heap și o deține; folosit pentru tipuri recursive și pentru Box<dyn Trait>.