Ce face borrowing și referințe (& și &mut)
Un împrumut îți dă acces la o valoare fără să devii proprietarul ei. &x creează o referință imuabilă (poți doar citi), iar &mut x o referință mutabilă (poți modifica, dacă x a fost declarat cu mut). Când împrumutul se termină, proprietarul își folosește valoarea în continuare. Funcțiile care doar citesc primesc &T, cele care modifică primesc &mut T.
Regula de aur, verificată de compilator (borrow checker): la un moment dat poți avea oricâte referințe & la o valoare sau o singură referință &mut, niciodată ambele. Astfel nu poate exista cineva care citește o valoare în timp ce altcineva o modifică. De exemplu, o referință la un element dintr-un Vec ar deveni invalidă dacă push mută vectorul în altă zonă de memorie; Rust oprește asta la compilare (E0502).
Un împrumut durează până la ultima lui folosire, nu până la sfârșitul blocului (non-lexical lifetimes). De aceea codul care termină de folosit o referință înainte de a modifica valoarea se compilează. A doua garanție: o referință nu poate trăi mai mult decât valoarea la care arată; nu există „dangling pointers”. Prin *r ajungi la valoarea din spatele referinței; la apelul de metode și la afișare, Rust face asta automat.
Exemple
Exemplul 1: Funcții care împrumută pentru citire și pentru modificare
afiseaza primește &Vec și doar citește. adauga_bonus primește &mut Vec și modifică fiecare notă. După ambele apeluri, note aparține tot lui main.
fn afiseaza(note: &[u32]) {
println!("Note: {note:?}");
}
fn adauga_bonus(note: &mut Vec<u32>, bonus: u32) {
for nota in note.iter_mut() {
*nota = (*nota + bonus).min(10);
}
note.push(10);
}
fn main() {
let mut note = vec![7, 9, 10];
afiseaza(¬e);
adauga_bonus(&mut note, 1);
afiseaza(¬e);
println!("Număr de note: {}", note.len());
}
Rezultat
Note: [7, 9, 10]
Note: [8, 10, 10, 10]
Număr de note: 4
Exemplul 2: Eroarea E0502: citire și modificare în același timp
prima este o referință la primul element. push ar putea muta tot vectorul în altă zonă de memorie (dacă nu mai are loc), iar prima ar arăta spre memorie eliberată. Pentru că prima este folosită după push, compilatorul refuză programul.
fn main() {
let mut note = vec![9, 7, 10];
let prima = ¬e[0];
note.push(8);
println!("Prima notă: {prima}");
}
Mesajul compilatorului (programul nu se compilează)
error[E0502]: cannot borrow `note` as mutable because it is also borrowed as immutable
--> main.rs:4:5
|
3 | let prima = ¬e[0];
| ---- immutable borrow occurs here
4 | note.push(8);
| ^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("Prima notă: {prima}");
| ----- immutable borrow later used here
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0502`.
Exemplul 3: Împrumutul se termină la ultima folosire
Aceleași operații ca mai sus, în altă ordine: prima este copiată și folosită înainte de push, deci împrumutul s-a terminat și modificarea este permisă. Al doilea bloc arată mai multe referințe de citire în același timp, ceea ce este mereu permis.
fn main() {
let mut note = vec![9, 7, 10];
let prima = note[0];
let ultima = ¬e[note.len() - 1];
println!("Prima notă: {prima}, ultima: {ultima}");
note.push(8);
println!("După push: {note:?}");
let a = ¬e;
let b = ¬e;
println!("{} și {} elemente: două referințe de citire simultane", a.len(), b.len());
let m = &mut note;
m.sort();
println!("Sortate: {note:?}");
}
Rezultat
Prima notă: 9, ultima: 10
După push: [9, 7, 10, 8]
4 și 4 elemente: două referințe de citire simultane
Sortate: [7, 8, 9, 10]
Greșeli frecvente
- Păstrezi o referință la un element (
&v[0]) și apoi apelezipush,insertsauclearpe vector: E0502. Copiază valoarea (let x = v[0];) sau termină de folosit referința înainte de modificare. - Uiți
mutfie la variabilă, fie la referință:&mut xcerelet mut x, iar funcția care modifică trebuie să primească&mut Tși să fie apelată cu&mut x. - Returnezi o referință la o variabilă locală (
fn f() -> &String { let s = ...; &s }). Variabila dispare la sfârșitul funcției, deci compilatorul refuză; returnează valoarea însăși (String). - Iei două referințe
&mutla elemente diferite ale aceluiași vector (&mut v[0]și&mut v[1]). Compilatorul nu știe că sunt diferite; foloseștev.split_at_mut(1)sauv.swap(0, 1).
Comenzi înrudite
ownership (proprietatea asupra valorilor): Regula de bază a lui Rust: fiecare valoare are un singur proprietar, iar când proprietarul dispare, valoarea este eliberată automat.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ă.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.for … in: Parcurge pe rând fiecare element al unui interval, al unei colecții sau al unui iterator.struct: Definește un tip nou care grupează mai multe valori cu nume (câmpuri), de exemplu un elev cu nume, clasă și medie.