Ce face Fn, FnMut și FnOnce
Orice closure (|x| x + 1) și orice funcție implementează automat unul sau mai multe dintre aceste trait-uri, după ce face cu variabilele capturate din jur. Fn: doar le citește, deci poate fi apelat oricât, chiar și în paralel. FnMut: le modifică (un contor, un vector în care adaugă), deci poate fi apelat de mai multe ori, dar are nevoie de acces exclusiv (mut). FnOnce: le consumă (mută un String afară, îl dă altei funcții), deci poate fi apelat o singură dată.
Ele formează o ierarhie: orice Fn este și FnMut, iar orice FnMut este și FnOnce. Când scrii o funcție care primește un closure, cere trait-ul cel mai permisiv de care ai nevoie: FnOnce dacă îl apelezi o dată (Option::map, unwrap_or_else, thread::spawn), FnMut dacă îl apelezi în buclă și poate modifica stare (majoritatea adaptoarelor: map, filter, for_each), Fn dacă îl apelezi de mai multe ori fără acces exclusiv.
Cuvântul move în fața unui closure (move || ...) îl face să preia proprietatea variabilelor capturate, în loc să le împrumute. move nu schimbă singur trait-ul: un closure move care doar citește rămâne Fn. Parametrii se scriu ca impl Fn(i32) -> i32, ca generic (F: FnMut(&str)) sau, pentru a stoca closure-uri diferite, ca Box<dyn Fn(i32) -> i32>.
Parametri
| Parametru | Descriere |
|---|---|
Args |
Tipurile parametrilor, scrise între paranteze: Fn(i32, &str). |
Output |
Tipul returnat, după ->: Fn(i32) -> bool. Fără săgeată, returnează (). |
Ce returnează
Apelul unui closure returnează Output.
Exemple
Exemplul 1: Trei closure-uri, trei trait-uri
salut doar citește (Fn), numara modifică un contor (FnMut, deci variabila trebuie să fie mut), iar preda mută String-ul afară (FnOnce, deci a doua apelare nu ar compila).
fn main() {
let nume = String::from("Ana");
let salut = || println!("Salut, {nume}!");
salut();
salut();
let mut vizite = 0;
let mut numara = || {
vizite += 1;
println!("vizita {vizite}");
};
numara();
numara();
let mesaj = String::from("cheia de la birou");
let preda = move || mesaj;
let primit = preda();
println!("am primit: {primit}");
println!("{nume} a avut {vizite} vizite");
}
Rezultat
Salut, Ana!
Salut, Ana!
vizita 1
vizita 2
am primit: cheia de la birou
Ana a avut 2 vizite
Exemplul 2: Funcții care cer fiecare trait
de_doua_ori apelează closure-ul de două ori, deci cere Fn. de_n_ori acceptă și closure-uri care modifică stare (FnMut). o_data acceptă orice, chiar și un closure care consumă (FnOnce).
fn de_doua_ori(f: impl Fn(i32) -> i32, x: i32) -> i32 {
f(f(x))
}
fn de_n_ori(n: usize, mut f: impl FnMut(usize)) {
for i in 0..n {
f(i);
}
}
fn o_data(f: impl FnOnce() -> String) -> String {
f()
}
fn main() {
let bonus = 3;
println!("{}", de_doua_ori(|x| x + bonus, 10));
println!("{}", de_doua_ori(|x| x * x, 3));
let mut jurnal = Vec::new();
de_n_ori(3, |i| jurnal.push(format!("pasul {i}")));
println!("{:?}", jurnal);
let raport = String::from("raport final");
println!("{}", o_data(move || raport + " (trimis)"));
}
Rezultat
16
81
["pasul 0", "pasul 1", "pasul 2"]
raport final (trimis)
Exemplul 3: Closure-uri păstrate și returnate
Box<dyn Fn> permite un vector de closure-uri diferite. reducere returnează un closure care a capturat procentul cu move.
fn reducere(procent: f64) -> impl Fn(f64) -> f64 {
move |pret| pret * (1.0 - procent / 100.0)
}
fn main() {
let operatii: Vec<(&str, Box<dyn Fn(f64) -> f64>)> = vec![
("cu TVA", Box::new(|p| p * 1.21)),
("reducere 10%", Box::new(reducere(10.0))),
("rotunjit", Box::new(|p: f64| p.round())),
];
let pret = 49.9;
for (nume, f) in &operatii {
println!("{nume:13} {:.2} lei", f(pret));
}
let black_friday = reducere(40.0);
println!("Black Friday: {:.2} lei", black_friday(250.0));
}
Rezultat
cu TVA 60.38 lei
reducere 10% 44.91 lei
rotunjit 50.00 lei
Black Friday: 150.00 lei
Greșeli frecvente
- Apelezi de două ori un closure care consumă o variabilă capturată: closure cannot be invoked more than once because it moves the variable `x` out of its environment. Clonează valoarea în closure sau apelează-l o singură dată.
- Declari fără
mutun closure care modifică stare: cannot borrow `f` as mutable, as it is not declared as mutable. Scrielet mut f = || .... - Ceri
Fnîntr-o funcție care primește closure-uri ce modifică stare: expected a closure that implements the `Fn` trait, but this closure only implements `FnMut`. CereFnMut(sauFnOnce, dacă îl apelezi o dată). - Returnezi dintr-o funcție un closure care împrumută variabile locale, fără
move: closure may outlive the current function. Adaugămove.
Comenzi înrudite
Iterator::map: Transformă fiecare valoare a iteratorului cu o funcție și dă un iterator cu rezultatele; nu calculează nimic până la consumare.iter::from_fn: Creează un iterator dintr-un closure: fiecare apel al closure-ului dă următorul element, iar None oprește iteratorul.Entry::or_insert_with: Ca or_insert, dar valoarea de inserat e calculată de un closure, apelat doar dacă cheia lipsește.Iterator::for_each: Apelează o funcție pentru fiecare element; este echivalentul unei bucle for, scris la capătul unui lanț de adaptoare.closures și move: Funcții anonime, scrise cu |parametri|, care pot folosi variabilele din jur. move le dă proprietatea asupra acelor variabile.