Box în Rust

Pune o valoare în memoria heap și o deține; folosit pentru tipuri recursive și pentru Box<dyn Trait>.

Sintaxă
let b: Box<T> = Box::new(valoare);

fn new(x: T) -> Box<T>

Ce face Box

Box::new(x) mută valoarea x în heap (memoria alocată dinamic) și îți dă un Box<T>, un pointer care o deține. Când box-ul iese din domeniu, valoarea este eliberată automat. Folosești box-ul ca pe valoarea însăși: *b o citește, iar metodele lui T se apelează direct (b.len()).

Ai nevoie de Box în trei situații: tipuri recursive (o listă sau un arbore care se conține pe el însuși, a cărui dimensiune altfel nu ar putea fi calculată), obiecte de tipuri diferite care implementează același trait (Vec<Box<dyn Forma>>) și valori mari pe care vrei să le muți ieftin (se copiază doar pointerul).

Un Box are un singur proprietar. Pentru o valoare deținută de mai mulți, folosește Rc sau Arc.

Parametri

Parametru Descriere
x Valoarea mutată în heap.

Ce returnează

Un Box<T> care deține valoarea.

Exemple

Exemplul 1: Un tip recursiv: lista de sarcini

Fără Box, enum-ul Lista s-ar conține pe el însuși direct și ar avea dimensiune infinită (eroarea recursive type has infinite size).

enum Lista {
    Element(&'static str, Box<Lista>),
    Gol,
}

fn afiseaza(lista: &Lista) {
    match lista {
        Lista::Element(sarcina, urmatorul) => {
            println!("- {sarcina}");
            afiseaza(urmatorul);
        }
        Lista::Gol => println!("(sfârșit)"),
    }
}

fn main() {
    let lista = Lista::Element(
        "cumpără pâine",
        Box::new(Lista::Element("sună-o pe Ana", Box::new(Lista::Gol))),
    );
    afiseaza(&lista);
}

Rezultat

- cumpără pâine
- sună-o pe Ana
(sfârșit)

Exemplul 2: Box: obiecte de tipuri diferite într-un vector

Cercul și dreptunghiul au tipuri diferite, dar ambele implementează Forma; în box le putem ține în același vector.

trait Forma {
    fn aria(&self) -> f64;
    fn nume(&self) -> String;
}

struct Cerc {
    raza: f64,
}

struct Dreptunghi {
    l: f64,
    h: f64,
}

impl Forma for Cerc {
    fn aria(&self) -> f64 {
        std::f64::consts::PI * self.raza * self.raza
    }
    fn nume(&self) -> String {
        format!("cerc (r = {})", self.raza)
    }
}

impl Forma for Dreptunghi {
    fn aria(&self) -> f64 {
        self.l * self.h
    }
    fn nume(&self) -> String {
        format!("dreptunghi {} x {}", self.l, self.h)
    }
}

fn main() {
    let forme: Vec<Box<dyn Forma>> = vec![
        Box::new(Cerc { raza: 1.5 }),
        Box::new(Dreptunghi { l: 4.0, h: 2.5 }),
    ];
    for f in &forme {
        println!("{}: aria = {:.2}", f.nume(), f.aria());
    }
}

Rezultat

cerc (r = 1.5): aria = 7.07
dreptunghi 4 x 2.5: aria = 10.00

Exemplul 3: Folosirea valorii din box

fn main() {
    let numar = Box::new(41);
    let urmator = *numar + 1;
    println!("{numar} -> {urmator}");

    let mut oras = Box::new(String::from("Iași"));
    oras.push_str(", România");
    println!("{oras} are {} octeți", oras.len());
}

Rezultat

41 -> 42
Iași, România are 15 octeți

Greșeli frecvente

  • Pui în Box valori mici fără motiv (Box<i32>): adaugi o alocare și o indirectare inutile.
  • Încerci să copiezi un box cu let b2 = b; și apoi folosești b: box-ul a fost mutat. Folosește b.clone() dacă T: Clone sau Rc pentru proprietate comună.

Comenzi înrudite

  • Box::leak: Renunță la un Box fără să elibereze memoria și dă o referință valabilă până la finalul programului (&’static).
  • Rc: Pointer cu numărarea referințelor: mai mulți proprietari împart aceeași valoare, eliberată când dispare ultimul.
  • Arc: Varianta lui Rc care poate fi împărțită între fire: contorul de proprietari este actualizat atomic.
  • mem::size_of: Returnează câți octeți ocupă în memorie o valoare de tipul T (fără ce alocă eventual în heap).