RefCell în Rust

Permite modificarea unei valori prin referință comună (&), verificând regulile de împrumut la rulare, nu la compilare.

Sintaxă
use std::cell::RefCell;

fn new(value: T) -> RefCell<T>
fn borrow(&self) -> Ref<'_, T>
fn borrow_mut(&self) -> RefMut<'_, T>

Ce face RefCell

Regula de bază din Rust: ori mai mulți cititori (&T), ori un singur modificator (&mut T). De obicei compilatorul verifică regula. RefCell<T> mută verificarea la rulare: poți modifica valoarea chiar dacă ai doar &RefCell. Acest lucru se numește mutabilitate interioară.

borrow() dă un împrumut de citire (Ref), borrow_mut() unul de scriere (RefMut). Împrumutul ține până când variabila Ref/RefMut dispare. Dacă încalci regula (de exemplu ceri borrow_mut cât timp există alt împrumut), programul intră în panică. Variantele try_borrow și try_borrow_mut returnează Result în loc de panică.

Cel mai des apare ca Rc<RefCell<T>>: mai mulți proprietari care pot modifica aceeași valoare. Nu poate fi folosit între fire; acolo există Mutex și RwLock.

Parametri

Parametru Descriere
value Valoarea inițială (la new).

Ce returnează

borrow: Ref<T> (se folosește ca &T). borrow_mut: RefMut<T> (se folosește ca &mut T).

Exemple

Exemplul 1: Coș de cumpărături comun

Ana și Dan țin același coș prin Rc; fiecare adaugă produse prin borrow_mut, deși au doar referințe comune.

use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let cos = Rc::new(RefCell::new(Vec::new()));
    let cos_ana = Rc::clone(&cos);
    let cos_dan = Rc::clone(&cos);

    cos_ana.borrow_mut().push("lapte");
    cos_dan.borrow_mut().push("pâine");
    cos_ana.borrow_mut().push("ouă");

    println!("coș: {:?}", cos.borrow());
    println!("produse: {}", cos.borrow().len());
}

Rezultat

coș: ["lapte", "pâine", "ouă"]
produse: 3

Exemplul 2: Modificare prin &self

Metoda cauta primește &self, dar ține un jurnal al căutărilor în RefCell.

use std::cell::RefCell;

struct Catalog {
    elevi: Vec<(&'static str, u8)>,
    jurnal: RefCell<Vec<String>>,
}

impl Catalog {
    fn cauta(&self, nume: &str) -> Option<u8> {
        self.jurnal.borrow_mut().push(format!("căutare: {nume}"));
        self.elevi.iter().find(|(n, _)| *n == nume).map(|(_, nota)| *nota)
    }
}

fn main() {
    let catalog = Catalog { elevi: vec![("Ana", 10), ("Mihai", 8)], jurnal: RefCell::new(Vec::new()) };
    println!("{:?}", catalog.cauta("Mihai"));
    println!("{:?}", catalog.cauta("Radu"));
    for linie in catalog.jurnal.borrow().iter() {
        println!("{linie}");
    }
}

Rezultat

Some(8)
None
căutare: Mihai
căutare: Radu

Exemplul 3: Două împrumuturi în același timp

try_borrow_mut arată conflictul fără panică. Cu borrow_mut, aceeași situație ar opri programul cu RefCell already borrowed.

use std::cell::RefCell;

fn main() {
    let note = RefCell::new(vec![9, 7]);

    let citire = note.borrow();
    println!("citesc: {:?}", *citire);
    println!("scriere în timpul citirii: {:?}", note.try_borrow_mut().map(|_| ()));
    drop(citire);

    note.borrow_mut().push(10);
    println!("după: {:?}", note.borrow());
}

Rezultat

citesc: [9, 7]
scriere în timpul citirii: Err(BorrowMutError)
după: [9, 7, 10]

Greșeli frecvente

  • Ții un borrow() într-o variabilă și apoi apelezi borrow_mut() în aceeași funcție: panică la rulare. Limitează împrumutul la un bloc sau eliberează-l cu drop.
  • Folosești RefCell peste tot ca să „scapi” de compilator. Verificările se mută la rulare și erorile devin panici; folosește-l doar unde chiar ai nevoie.
  • Pentru valori Copy simple (numere, bool), Cell este mai simplu și nu poate intra în panică.

Comenzi înrudite

  • RefCell::borrow: borrow() împrumută valoarea din RefCell pentru citire, borrow_mut() pentru scriere; conflictele dau panică.
  • Cell: Celulă cu mutabilitate interioară pentru valori Copy: get() copiază valoarea afară, set() pune una nouă, prin &self.
  • Rc: Pointer cu numărarea referințelor: mai mulți proprietari împart aceeași valoare, eliberată când dispare ultimul.
  • Mutex: Protejează o valoare folosită din mai multe fire: lock() așteaptă până când firul curent este singurul cu acces.