Ce face closures și move
Un closure este o funcție fără nume, scrisă pe loc: |x| x * 2. Parametrii stau între bare verticale, tipurile sunt de obicei deduse, iar corpul este o expresie sau un bloc. Closures se folosesc peste tot cu iteratorii (map, filter, sort_by_key) și ca funcții transmise altor funcții.
Spre deosebire de fn, un closure poate captura variabile din jur. Rust alege cel mai blând mod de captură: prin referință (dacă doar citește), prin referință mutabilă (dacă modifică) sau prin mutare (dacă trebuie să dețină valoarea). De aici cele trei trait-uri: Fn (poate fi apelat de oricâte ori, doar citește), FnMut (modifică ce a capturat) și FnOnce (consumă ce a capturat, deci poate fi apelat o dată).
move înaintea closure-ului îl obligă să ia proprietatea asupra tuturor variabilelor folosite. Este necesar când closure-ul trăiește mai mult decât funcția care l-a creat: un fir de execuție (thread::spawn(move || ...)) sau un closure returnat dintr-o funcție. Pentru tipurile Copy (numere), move le copiază.
Exemple
Exemplul 1: Closures cu iteratori
prag este capturat prin referință în closure-ul din filter. sort_by primește un closure cu doi parametri care compară două produse.
fn main() {
let mut produse: Vec<(&str, f64)> = vec![("lapte", 9.5), ("pâine", 5.0), ("cafea", 32.9), ("unt", 12.4)];
let prag = 10.0;
let scumpe: Vec<&str> = produse.iter()
.filter(|(_, pret)| *pret > prag)
.map(|(nume, _)| *nume)
.collect();
println!("Peste {prag} lei: {scumpe:?}");
produse.sort_by(|a, b| b.1.total_cmp(&a.1));
println!("De la cel mai scump: {produse:?}");
let cu_tva = |pret: f64| -> f64 { (pret * 1.21 * 100.0).round() / 100.0 };
println!("Cafea cu TVA: {}", cu_tva(32.9));
}
Rezultat
Peste 10 lei: ["cafea", "unt"]
De la cel mai scump: [("cafea", 32.9), ("unt", 12.4), ("lapte", 9.5), ("pâine", 5.0)]
Cafea cu TVA: 39.81
Exemplul 2: FnMut: un closure care modifică ce a capturat
inregistreaza modifică total, deci este FnMut și trebuie declarat cu let mut. După ultima folosire a closure-ului, total poate fi citit din nou direct.
fn main() {
let mut total = 0;
let mut inregistreaza = |suma: i32| {
total += suma;
println!("+{suma} lei");
};
inregistreaza(120);
inregistreaza(45);
inregistreaza(300);
println!("Total: {total} lei");
}
Rezultat
+120 lei
+45 lei
+300 lei
Total: 465 lei
Exemplul 3: move: closure trimis într-un alt fir de execuție
Firul nou poate trăi mai mult decât main, deci nu poate împrumuta elevi. move mută vectorul în closure; firul îl prelucrează și returnează rezultatul prin join.
use std::thread;
fn main() {
let elevi = vec![String::from("Ana"), String::from("Mihai"), String::from("Ioana")];
let fir = thread::spawn(move || {
let mut nume = elevi.join(", ");
nume.push_str(&format!(" ({} elevi)", elevi.len()));
nume
});
let rezultat = fir.join().unwrap();
println!("{rezultat}");
}
Rezultat
Ana, Mihai, Ioana (3 elevi)
Greșeli frecvente
- Trimiți un closure într-un fir fără
move: eroarea E0373closure may outlive the current function, but it borrows `x`. Adaugămove. - Folosești o variabilă după ce a fost mutată într-un closure
move. Dacă ai nevoie de ea în ambele locuri, clonează-o înainte (let copie = x.clone();) și mută copia. - Declari fără
mutun closure care modifică ceva:cannot borrow `f` as mutable. Un closureFnMutse ține într-o variabilălet mut.
Comenzi înrudite
fn: Declară o funcție. Tipul fiecărui parametru este obligatoriu, iar ultima expresie fără punct și virgulă este valoarea returnată.impl Trait: Scrie „un tip care implementează Trait” fără să-l numești: ca parametru este o scurtătură pentru generics, ca rezultat ascunde tipul exact.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.for … in: Parcurge pe rând fiecare element al unui interval, al unei colecții sau al unui iterator.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.