ownership (proprietatea asupra valorilor) în Rust

Regula de bază a lui Rust: fiecare valoare are un singur proprietar, iar când proprietarul dispare, valoarea este eliberată automat.

Sintaxă
let a = String::from("Ana");
let b = a;          // MUTARE: b este noul proprietar, a nu mai poate fi folosit
let c = b.clone();  // COPIE completă: b și c au fiecare textul lor
let x = 5;
let y = x;          // tipurile Copy (numere, bool, char) se copiază

Ce face ownership (proprietatea asupra valorilor)

Ownership („proprietate”) este felul în care Rust eliberează memoria fără garbage collector și fără free scris de mână. Sunt trei reguli: (1) fiecare valoare are un proprietar (o variabilă); (2) o valoare are un singur proprietar la un moment dat; (3) când proprietarul iese din domeniu (la }), valoarea este eliberată automat (se apelează drop).

Consecința cea mai importantă este mutarea (move). Pentru tipurile care dețin memorie, ca String, Vec sau Box, let b = a; nu copiază datele: mută proprietatea la b. De acum a este considerată goală și orice folosire a ei este eroare de compilare (E0382, borrow of moved value). Așa nu pot exista doi proprietari care să elibereze aceeași memorie de două ori. Același lucru se întâmplă când dai valoarea unei funcții (afiseaza(a)) sau o pui într-un vector.

Tipurile simple, care stau în întregime pe stivă (numere, bool, char, tupluri și array-uri din ele), implementează Copy: atribuirea le copiază, iar ambele variabile rămân valide. Pentru o copie completă a unui String sau Vec apelezi explicit .clone(). Dacă vrei doar să te uiți la o valoare fără să o iei, o împrumuți cu & – vezi pagina despre borrowing.

Exemple

Exemplul 1: Mutare, clonare și copiere

oras este clonat, deci ambele variabile au textul lor. nota este i32, un tip Copy, deci alta_nota primește o copie. nume este mutat în proprietar_nou, iar după aceea doar proprietar_nou poate fi folosit.

fn main() {
    let oras = String::from("Iași");
    let copie = oras.clone();
    println!("oras = {oras}, copie = {copie}");

    let nota = 9;
    let alta_nota = nota;
    println!("nota = {nota}, alta_nota = {alta_nota}");

    let nume = String::from("Ana");
    let proprietar_nou = nume;
    println!("proprietar_nou = {proprietar_nou}");
}

Rezultat

oras = Iași, copie = Iași
nota = 9, alta_nota = 9
proprietar_nou = Ana

Exemplul 2: Eroarea E0382: folosirea unei valori mutate

După let alt_nume = nume;, textul aparține lui alt_nume. Compilatorul arată unde s-a făcut mutarea, unde este folosită valoarea după mutare și sugerează .clone().

fn main() {
    let nume = String::from("Ana");
    let alt_nume = nume;
    println!("{alt_nume}");
    println!("{nume}");
}

Mesajul compilatorului (programul nu se compilează)

error[E0382]: borrow of moved value: `nume`
 --> main.rs:5:16
  |
2 |     let nume = String::from("Ana");
  |         ---- move occurs because `nume` has type `String`, which does not implement the `Copy` trait
3 |     let alt_nume = nume;
  |                    ---- value moved here
4 |     println!("{alt_nume}");
5 |     println!("{nume}");
  |                ^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let alt_nume = nume.clone();
  |                        ++++++++

error: aborting due to 1 previous error

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

Exemplul 3: Mutarea în și din funcții

adauga_titlu primește String (deci devine proprietarul) și îl returnează modificat, deci proprietatea se întoarce la apelant. Structura Fisier implementează Drop, care afișează un mesaj când valoarea este eliberată: _temporar dispare la }-ul blocului interior, iar _raport la sfârșitul lui main, fără niciun apel scris de mână.

struct Fisier {
    nume: String,
}

impl Drop for Fisier {
    fn drop(&mut self) {
        println!("(eliberez {})", self.nume);
    }
}

fn adauga_titlu(mut text: String) -> String {
    text.insert_str(0, "Dr. ");
    text
}

fn main() {
    let medic = String::from("Ionescu");
    let medic = adauga_titlu(medic);
    println!("{medic}");

    {
        let _temporar = Fisier { nume: "temp.txt".to_string() };
        println!("în bloc");
    }
    let _raport = Fisier { nume: "raport.pdf".to_string() };
    println!("sfârșitul lui main");
}

Rezultat

Dr. Ionescu
în bloc
(eliberez temp.txt)
sfârșitul lui main
(eliberez raport.pdf)

Exemplul 4: Mutarea într-o funcție și cum o eviți

numara_litere are nevoie doar să citească textul, deci primește &str (un împrumut). Astfel oras rămâne al lui main și poate fi folosit după apel.

fn numara_litere(text: &str) -> usize {
    text.chars().filter(|c| c.is_alphabetic()).count()
}

fn main() {
    let oras = String::from("Cluj-Napoca");
    let litere = numara_litere(&oras);
    println!("{oras} are {litere} litere");
}

Rezultat

Cluj-Napoca are 10 litere

Greșeli frecvente

  • Rezolvi orice eroare E0382 cu .clone(). Funcționează, dar copiază datele de fiecare dată. De cele mai multe ori soluția corectă este un împrumut: funcția primește &T sau &str în loc de T.
  • Parcurgi un vector cu for x in v și apoi folosești v: bucla a mutat vectorul. Scrie for x in &v.
  • Crezi că let b = a; copiază un String ca în alte limbaje. Copiază doar „mânerul” (pointer, lungime, capacitate) și mută proprietatea; textul propriu-zis nu este duplicat.
  • Încerci să muți un câmp dintr-o structură împrumutată (let n = elev.nume; când ai doar &Elev): eroarea E0507 cannot move out of. Folosește &elev.nume sau elev.nume.clone().

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.
  • lifetimes (durate de viață, 'a): 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ă.
  • closures și move: Funcții anonime, scrise cu |parametri|, care pot folosi variabilele din jur. move le dă proprietatea asupra acelor variabile.
  • variabile și mutabilitate (let, mut): let creează o variabilă. Implicit nu poate fi modificată; cu let mut primește voie să-și schimbe valoarea.
  • slice-uri (&[T] și &str): O „fereastră” spre o parte dintr-un array, Vec sau text: o referință care știe de unde începe și câte elemente are, fără să copieze nimic.