Chiusure
Le chiusure (closure) di Rust sono funzioni anonime che è possibile salvare in una variabile o passare come argomenti ad altre funzioni. È possibile creare la chiusura in un punto e poi chiamarla altrove per valutarla in un contesto diverso. A differenza delle funzioni, le chiusure possono catturare valori dallo scope in cui sono definite. Dimostreremo come queste funzionalità di chiusura consentano il riutilizzo del codice e la personalizzazione del comportamento.
Catturare l’Ambiente
Esamineremo innanzitutto come possiamo utilizzare le chiusure per catturare valori dall’ambiente in cui sono definite per un uso successivo. Ecco lo scenario: ogni tanto, la nostra azienda di magliette regala una maglietta esclusiva in edizione limitata a qualcuno nella nostra mailing list come promozione. Gli utenti della mailing list possono facoltativamente aggiungere il loro colore preferito al proprio profilo. Se la persona a cui viene assegnata una maglietta gratuita ha impostato il suo colore preferito, riceverà la maglietta di quel colore. Se la persona non ha specificato un colore preferito, riceverà il colore di cui l’azienda ha attualmente la maggiore disponibilità.
Ci sono molti modi per implementarlo. Per questo esempio, useremo un’enum
chiamata ColoreMaglietta che ha le varianti Rosso e Blu (limitando il
numero di colori disponibili per semplicità). Rappresentiamo l’inventario
dell’azienda con una struct Inventario che ha un campo denominato
magliette che contiene un Vec<ColoreMaglietta> che rappresenta i colori
delle magliette attualmente disponibili in magazzino. Il metodo regalo
definito su Inventario ottiene la preferenza opzionale per il colore della
maglietta del vincitore della maglietta gratuita e restituisce il colore della
maglietta che la persona riceverà. Questa configurazione è mostrata nel Listato
13-1.
#[derive(Debug, PartialEq, Copy, Clone)]
enum ColoreMaglietta {
Rosso,
Blu,
}
struct Inventario {
magliette: Vec<ColoreMaglietta>,
}
impl Inventario {
fn regalo(&self, preferenze_utente: Option<ColoreMaglietta>) -> ColoreMaglietta {
preferenze_utente.unwrap_or_else(|| self.maggior_stock())
}
fn maggior_stock(&self) -> ColoreMaglietta {
let mut num_rosso = 0;
let mut num_blu = 0;
for colore in &self.magliette {
match colore {
ColoreMaglietta::Rosso => num_rosso += 1,
ColoreMaglietta::Blu => num_blu += 1,
}
}
if num_rosso > num_blu {
ColoreMaglietta::Rosso
} else {
ColoreMaglietta::Blu
}
}
}
fn main() {
let negozio = Inventario {
magliette: vec![ColoreMaglietta::Blu, ColoreMaglietta::Rosso, ColoreMaglietta::Blu],
};
let pref_utente1 = Some(ColoreMaglietta::Rosso);
let regalo1 = negozio.regalo(pref_utente1);
println!(
"L'utente con preferenza {:?} riceve {:?}",
pref_utente1, regalo1
);
let pref_utente2 = None;
let regalo2 = negozio.regalo(pref_utente2);
println!(
"L'utente con preferenza {:?} riceve {:?}",
pref_utente2, regalo2
);
}
Il negozio definito in main ha due magliette blu e una rossa rimanenti da
distribuire per questa promozione in edizione limitata. Chiamiamo il metodo
regalo per un utente con preferenza per una maglietta rossa e un utente senza
alcuna preferenza.
Anche in questo caso, questo codice potrebbe essere implementato in molti modi
e, per concentrarci sulle chiusure, ci siamo attenuti ai concetti che hai già
imparato, ad eccezione del corpo del metodo regalo che utilizza una chiusura.
Nel metodo regalo, otteniamo la preferenza dell’utente come parametro di
type Option<ColoreMaglietta> e chiamiamo il metodo unwrap_or_else su
preferenza_utente. Il metodo unwrap_or_else su
Option<T> è definito dalla libreria standard.
Accetta un argomento: una chiusura senza argomenti che restituisce un valore T
(lo stesso type memorizzato nella variante Some di Option<T>, in questo
caso ColoreMaglietta). Se Option<T> è la variante Some, unwrap_or_else
restituisce il valore presente all’interno di Some. Se Option<T> è la
variante None , unwrap_or_else chiama la chiusura e restituisce il valore
restituito dalla chiusura.
Specifichiamo l’espressione di chiusura || self.maggior_stock() come argomento
di unwrap_or_else. Questa è una chiusura che non accetta parametri (se la
chiusura avesse parametri, questi apparirebbero tra le due barre verticali). Il
corpo della chiusura chiama self.maggior_stock(). Stiamo definendo la chiusura
qui, e l’implementazione di unwrap_or_else valuterà la chiusura in seguito, se
il risultato è necessario.
L’esecuzione di questo codice stampa quanto segue:
$ cargo run
Compiling azienda-magliette v0.1.0 (file:///progetti/azienda-magliette)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.07s
Running `target/debug/azienda-magliette`
L'utente con preferenza Some(Rosso) riceve Rosso
L'utente con preferenza None riceve Blu
Un aspetto interessante è che abbiamo passato una chiusura che chiama
self.maggior_stock() sull’istanza corrente di Inventario. La libreria
standard non aveva bisogno di sapere nulla sui type Inventario o
ColoreMaglietta che abbiamo definito, né sulla logica che vogliamo utilizzare
in questo scenario. La chiusura cattura un reference immutabile all’istanza
self di Inventario e lo passa con il codice che specifichiamo al metodo
unwrap_or_else. Le funzioni, d’altra parte, non sono in grado di catturare il
loro ambiente in questo modo.
Inferenza e Annotazione del Type Delle Chiusure
Esistono ulteriori differenze tra funzioni e chiusure. Le chiusure di solito non
richiedono di annotare i type dei parametri o dei valori di ritorno, come
fanno le funzioni fn. Le annotazioni del type sono necessarie sulle funzioni
perché i type fanno parte di un’interfaccia esplicita esposta agli utenti.
Definire rigidamente questa interfaccia è importante per garantire che tutti
concordino sui tipi di valori che una funzione utilizza e restituisce. Le
chiusure, d’altra parte, non vengono utilizzate in un’interfaccia esposta come
questa: vengono memorizzate in variabili e utilizzate senza denominarle ed
esporle agli utenti della nostra libreria.
Le chiusure sono in genere brevi e rilevanti solo in un contesto ristretto, piuttosto che in uno scenario arbitrario. In questi contesti limitati, il compilatore può dedurre i type dei parametri e il type restituito, in modo simile a come è in grado di dedurre i type della maggior parte delle variabili (ci sono rari casi in cui il compilatore necessita di annotazioni del type anche per le chiusure).
Come per le variabili, possiamo aggiungere annotazioni del type se vogliamo aumentare l’esplicitezza e la chiarezza, a costo di essere più prolissi del necessario. L’annotazione dei type per una chiusura sarebbe simile alla definizione mostrata nel Listato 13-2. In questo esempio, definiamo una chiusura e la memorizziamo in una variabile, anziché definirla nel punto in cui la passiamo come argomento, come abbiamo fatto nel Listato 13-1.
use std::thread; use std::time::Duration; fn genera_allenamento(intensità: u32, numero_casuale: u32) { let chiusura_lenta = |num: u32| -> u32 { println!("calcolo lentamente..."); thread::sleep(Duration::from_secs(2)); num }; if intensità < 25 { println!("Oggi, fai {} flessioni!", chiusura_lenta(intensità)); println!("Poi, fai {} piegamenti!", chiusura_lenta(intensità)); } else { if numero_casuale == 3 { println!("Oggi fai una pausa! Ricordati di idratarti!"); } else { println!( "Oggi, corri per {} minuti!", chiusura_lenta(intensità) ); } } } fn main() { let simulazione_numero_utente = 10; let simulazione_numero_casuale = 7; genera_allenamento(simulazione_numero_utente, simulazione_numero_casuale); }
Con l’aggiunta delle annotazioni del type, la sintassi delle chiusure appare più simile alla sintassi delle funzioni. Qui, per confronto, definiamo una funzione che aggiunge 1 al suo parametro e una chiusura che ha lo stesso comportamento. Abbiamo aggiunto alcuni spazi per allineare le parti rilevanti. Questo illustra come la sintassi delle chiusure sia simile a quella delle funzioni, fatta eccezione per l’uso delle barre verticali e per la quantità di sintassi che è facoltativa:
fn agg_uno_v1 (x: u32) -> u32 { x + 1 }
let agg_uno_v2 = |x: u32| -> u32 { x + 1 };
let agg_uno_v3 = |x| { x + 1 };
let agg_uno_v4 = |x| x + 1 ;
La prima riga mostra una definizione di funzione e la seconda una definizione di
chiusura completamente annotata. Nella terza riga, rimuoviamo le annotazioni del
type dalla definizione della chiusura. Nella quarta riga, rimuoviamo le
parentesi, che sono facoltative perché il corpo della chiusura ha una sola
espressione. Queste sono tutte definizioni valide che produrranno lo stesso
comportamento quando vengono chiamate. Le righe agg_uno_v3 e agg_uno_v4
richiedono che le chiusure vengano valutate per essere compilabili, poiché i
type verranno dedotti dal loro utilizzo. Questo è simile a let v = Vec::new(); che richiede annotazioni del type o valori di qualche tipo da
inserire in Vec affinché Rust possa dedurne il type.
Per le definizioni delle chiusure, il compilatore dedurrà un type concreto per
ciascuno dei loro parametri e per il loro valore di ritorno. Ad esempio, il
Listato 13-3 mostra la definizione di una chiusura breve che restituisce
semplicemente il valore ricevuto come parametro. Questa chiusura non è molto
utile, se non per gli scopi di questo esempio. Nota che non abbiamo aggiunto
alcuna annotazione del type alla definizione. Poiché non ci sono annotazioni,
possiamo chiamare la chiusura con qualsiasi type, come abbiamo fatto qui con
String la prima volta. Se poi proviamo a chiamare esempio_chiusura con un
intero, otterremo un errore.
fn main() {
let esempio_chiusura = |x| x;
let s = esempio_chiusura(String::from("ciao"));
let n = esempio_chiusura(5);
}
Il compilatore ci dà questo errore:
$ cargo run
Compiling esempio-chiusura v0.1.0 (file:///progetti/esempio_chiusura)
error[E0308]: mismatched types
--> src/main.rs:6:30
|
6 | let n = esempio_chiusura(5);
| ---------------- ^ expected `String`, found integer
| |
| arguments to this function are incorrect
|
note: expected because the closure was earlier called with an argument of type `String`
--> src/main.rs:5:30
|
5 | let s = esempio_chiusura(String::from("ciao"));
| ---------------- ^^^^^^^^^^^^^^^^^^^^ expected because this argument is of type `String`
| |
| in this closure call
note: closure parameter defined here
--> src/main.rs:3:29
|
3 | let esempio_chiusura = |x| x;
| ^
help: try using a conversion method
|
6 | let n = esempio_chiusura(5.to_string());
| ++++++++++++
For more information about this error, try `rustc --explain E0308`.
error: could not compile `esempio_chiusura` (bin "esempio_chiusura") due to 1 previous error
La prima volta che chiamiamo esempio_chiusura con il valore String, il
compilatore deduce che il type di x e il type di ritorno della chiusura
siano String. Questi type vengono quindi bloccati nella chiusura in
esempio_chiusura e si verifica un errore di type quando si tenta nuovamente
di utilizzare un type diverso con la stessa chiusura.
Catturare i Reference o Trasferire la Ownership
Le chiusure possono catturare valori dal loro ambiente in tre modi, che corrispondono direttamente ai tre modi in cui una funzione può accettare un parametro: un prestito immutabile, un prestito mutabile o prendendo la ownership. La chiusura deciderà quale di questi utilizzare in base a ciò che il corpo della funzione fa con i valori catturati.
Nel Listato 13-4, definiamo una chiusura che cattura un reference immutabile
al vettore denominato lista perché necessita solo di un riferimento immutabile
per stampare il valore.
fn main() { let lista = vec![1, 2, 3]; println!("Prima di definire la chiusura: {lista:?}"); let solo_prestito = || println!("Dalla chiusura: {lista:?}"); println!("Prima di chiamare la chiusura: {lista:?}"); solo_prestito(); println!("Dopo aver chiamato la chiusura: {lista:?}"); }
Questo esempio illustra anche che una variabile può essere associata a una definizione di chiusura, e che possiamo successivamente chiamare la chiusura utilizzando il nome della variabile e le parentesi come se il nome della variabile fosse il nome di una funzione.
Poiché possiamo avere più reference immutabili a lista contemporaneamente,
lista è comunque accessibile dal codice prima della definizione della
chiusura, dopo la definizione della chiusura ma prima che la chiusura venga
chiamata, e dopo che la chiusura viene chiamata. Questo codice si compila,
esegue e stampa:
$ cargo run
Compiling esempio-chiusura v0.1.0 (file:///progetti/esempio-chiusura)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.09s
Running `target/debug/esempio-chiusura`
Prima di definire la chiusura: [1, 2, 3]
Prima di chiamare la chiusura: [1, 2, 3]
Dalla chiusura: [1, 2, 3]
Dopo aver chiamato la chiusura: [1, 2, 3]
Successivamente, nel Listato 13-5, modifichiamo il corpo della chiusura in modo
che aggiunga un elemento al vettore list. La chiusura ora cattura un
reference mutabile.
fn main() { let mut lista = vec![1, 2, 3]; println!("Prima di definire la chiusura: {lista:?}"); let mut prestito_mutabile = || lista.push(7); prestito_mutabile(); println!("Dopo aver chiamato la chiusura: {lista:?}"); }
Questo codice si compila, esegue e stampa:
$ cargo run
Compiling esempio-chiusura v0.1.0 (file:///progetti/esempio-chiusura)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.08s
Running `target/debug/esempio-chiusura`
Prima di definire la chiusura: [1, 2, 3]
Dopo aver chiamato la chiusura: [1, 2, 3, 7]
Nota che non c’è più println! tra la definizione e la chiamata della chiusura
prestito_mutabile: quando prestito_mutabile è definita, cattura un
reference mutabile a lista. Non usiamo più la chiusura dopo che è stata
chiamata, quindi il prestito mutabile termina. Tra la definizione della chiusura
e la chiamata alla chiusura, non è consentito un prestito immutabile per
stampare perché, quando c’è un prestito mutabile, non sono consentiti altri
prestiti. Prova ad aggiungere println! per vedere quale messaggio di errore
ottieni!
Se vuoi forzare la chiusura ad assumere la ownership dei valori che usa
nell’ambiente, anche se il corpo della chiusura non ne ha strettamente bisogno,
puoi usare la parola chiave move prima dell’elenco dei parametri.
Questa tecnica è utile soprattutto quando si passa una chiusura a un nuovo
thread per spostare i dati in modo che siano di proprietà del nuovo thread.
Discuteremo i thread e perché dovreste utilizzarli in dettaglio nel Capitolo
16, quando parleremo di concorrenza, ma per ora, esploriamo brevemente la
creazione di un nuovo thread utilizzando una chiusura che richiede la parola
chiave move. Il Listato 13-6 mostra il Listato 13-4 modificato per stampare il
vettore in un nuovo thread anziché nel thread principale.
use std::thread; fn main() { let lista = vec![1, 2, 3]; println!("Prima di definire la chiusura: {lista:?}"); thread::spawn(move || println!("Dal thread: {lista:?}")) .join() .unwrap(); }
move per forzare la chiusura affinché il thread prenda la ownership di listaGeneriamo un nuovo thread, assegnandogli una chiusura da eseguire come
argomento. Il corpo della chiusura stampa la lista. Nel Listato 13-4, la
chiusura catturava solo lista utilizzando un reference immutabile, perché
questo rappresenta il minimo accesso a lista necessario per stamparlo. In
questo esempio, anche se il corpo della chiusura richiede ancora solo un
reference immutabile, dobbiamo specificare che lista debba essere spostato
nella chiusura inserendo la parola chiave move all’inizio della definizione
della chiusura. Se il thread principale eseguisse più operazioni prima di
chiamare join sul nuovo thread, il nuovo thread potrebbe terminare prima
del thread principale, oppure il thread principale potrebbe terminare per
primo. Se il thread principale mantenesse la ownership di lista ma
terminasse prima del nuovo thread e de-allocasse la memoria di lista, il
reference immutabile nel thread non sarebbe valido. Pertanto, il compilatore
richiede che lista venga spostato nella chiusura assegnata al nuovo thread,
affinché il reference sia valido. Prova a rimuovere la parola chiave move o
a utilizzare lista nel thread principale dopo la definizione della chiusura
per vedere quali errori del compilatore ottieni!
Restituire i Valori Catturati dalle Chiusure
Una volta che una chiusura ha catturato un reference o preso la ownership di un valore nell’ambiente in cui è definita (influenzando quindi cosa, se presente, viene spostato all’interno della chiusura), il codice nel corpo della chiusura definisce cosa succede ai reference o ai valori quando la chiusura viene valutata in seguito (influenzando quindi cosa, se presente, viene spostato fuori dalla chiusura).
Il corpo di una chiusura può eseguire una delle seguenti operazioni: spostare un valore catturato fuori dalla chiusura, mutare il valore catturato, non spostare né mutare il valore, oppure non catturare nulla dall’ambiente fin dall’inizio.
Il modo in cui una chiusura cattura e gestisce i valori dell’ambiente influenza
quali trait implementa la chiusura, e i trait sono il modo in cui funzioni e
struct possono specificare quali tipi di chiusure possono utilizzare. Le
chiusure implementeranno automaticamente uno, due o tutti e tre questi trait
Fn, in modo additivo, a seconda di come il corpo della chiusura gestisce i
valori:
FnOncesi applica alle chiusure che possono essere chiamate una sola volta. Tutte le chiusure implementano almeno questo trait perché tutte le chiusure possono essere chiamate. Una chiusura che sposta i valori catturati fuori dal suo corpo implementerà soloFnOncee nessuno degli altri traitFnperché può essere chiamata una sola volta.FnMutsi applica alle chiusure che non spostano i valori catturati fuori dal loro corpo, ma che potrebbero mutarli. Queste chiusure possono essere chiamate più di una volta.Fnsi applica alle chiusure che non spostano i valori catturati fuori dal loro corpo e che non mutano i valori catturati, così come alle chiusure che non catturano nulla dal loro ambiente. Queste chiusure possono essere chiamate più di una volta senza mutare il loro ambiente, il che è importante in casi come quando una chiusura viene chiamata più volte contemporaneamente.
Diamo un’occhiata alla definizione del metodo unwrap_or_else su Option<T>
che abbiamo usato nel Listato 13-1:
impl<T> Option<T> {
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T
{
match self {
Some(x) => x,
None => f(),
}
}
}
Ricorda che T è il type generico che rappresenta il type del valore nella
variante Some di un’Option. Quel T è anche il type restituito dalla
funzione unwrap_or_else: il codice che chiama unwrap_or_else su
un’Option<String>, ad esempio, otterrà una String.
Nota inoltre che la funzione unwrap_or_else ha il parametro di type generico
aggiuntivo F. F è il type del parametro denominato f, che è la chiusura
che forniamo quando chiamiamo unwrap_or_else.
Il vincolo di trait specificato sul type generico F è FnOnce() -> T, il
che significa che F deve poter essere chiamato una sola volta, non accettare
argomenti e restituire una T. L’utilizzo di FnOnce nel vincolo del trait
esprime il limite che unwrap_or_else non chiamerà f più di una volta. Nel
corpo di unwrap_or_else, possiamo vedere che se Option è Some, f non
verrà chiamata. Se Option è None, f verrà chiamata una volta. Poiché tutte
le chiusure implementano FnOnce, unwrap_or_else accetta tutti e tre i tipi
di chiusure ed è il più flessibile possibile.
Nota: se ciò che vogliamo fare non richiede l’acquisizione di un valore dall’ambiente, possiamo usare il nome di una funzione anziché una chiusura quando abbiamo bisogno di qualcosa che implementi uno dei trait
Fn. Ad esempio, su un valoreOption<Vec<T>>, potremmo chiamareunwrap_or_else(Vec::new)per ottenere un nuovo vettore vuoto se il valore èNone. Il compilatore implementa automaticamente qualsiasi dei traitFnapplicabile per una definizione di funzione.
Ora diamo un’occhiata al metodo della libreria standard sort_by_key, definito
sulle slice, per vedere in che modo differisce da unwrap_or_else e perché
sort_by_key utilizza FnMut invece di FnOnce come vincolo del trait. La
chiusura riceve un argomento sotto forma di reference all’elemento corrente
nella slice in esame e restituisce un valore di type K che può essere
ordinato. Questa funzione è utile quando si desidera ordinare una slice in
base a un particolare attributo di ciascun elemento. Nel Listato 13-7, abbiamo
un elenco di istanze di Rettangolo e utilizziamo sort_by_key per ordinarle
in base al loro attributo larghezza dal più stretto al più largo.
#[derive(Debug)] struct Rettangolo { larghezza: u32, altezza: u32, } fn main() { let mut lista = [ Rettangolo { larghezza: 10, altezza: 1 }, Rettangolo { larghezza: 3, altezza: 5 }, Rettangolo { larghezza: 7, altezza: 12 }, ]; lista.sort_by_key(|r| r.larghezza); println!("{lista:#?}"); }
sort_by_key per ordinare i rettangoli in base alla larghezzaQuesto codice stampa:
$ cargo run
Compiling rettangoli v0.1.0 (file:///progetti/rettangoli)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.15s
Running `target/debug/rettangoli`
[
Rettangolo {
larghezza: 3,
altezza: 5,
},
Rettangolo {
larghezza: 7,
altezza: 12,
},
Rettangolo {
larghezza: 10,
altezza: 1,
},
]
Il motivo per cui sort_by_key è definito per accettare una chiusura FnMut è
che chiama la chiusura più volte: una volta per ogni elemento nella slice. La
chiusura |r| r.larghezza non cattura, modifica o sposta nulla dal suo
ambiente, quindi soddisfa i requisiti del vincolo di trait.
Al contrario, il Listato 13-8 mostra un esempio di una chiusura che implementa
solo il trait FnOnce, perché sposta un valore fuori dall’ambiente. Il
compilatore non ci permette di usare questa chiusura con sort_by_key.
#[derive(Debug)]
struct Rettangolo {
larghezza: u32,
altezza: u32,
}
fn main() {
let mut lista = [
Rettangolo { larghezza: 10, altezza: 1 },
Rettangolo { larghezza: 3, altezza: 5 },
Rettangolo { larghezza: 7, altezza: 12 },
];
let mut azioni_ordinamento = vec![];
let valore = String::from("chiusura chiamata");
lista.sort_by_key(|r| {
azioni_ordinamento.push(valore);
r.larghezza
});
println!("{lista:#?}");
}
FnOnce con sort_by_keyQuesto è un modo artificioso e contorto (che non funziona) per provare a contare
il numero di volte in cui sort_by_key chiama la chiusura durante l’ordinamento
di lista. Questo codice tenta di effettuare questo conteggio inserendo
valore, una String dall’ambiente della chiusura, nel vettore
azioni_ordinamento. La chiusura cattura valore e quindi sposta valore
fuori dalla chiusura trasferendo la ownership di valore al vettore
azioni_ordinamento. Questa chiusura può essere chiamata una sola volta; se
provi a chiamarla una seconda volta non funzionerebbe perché valore non
sarebbe più nell’ambiente da inserire nuovamente in azioni_ordinamento!
Pertanto, questa chiusura implementa solo FnOnce. Quando proviamo a compilare
questo codice, otteniamo questo errore che indica che valore non può essere
spostato fuori dalla chiusura perché la chiusura deve implementare FnMut:
$ cargo run
Compiling rettangoli v0.1.0 (/file:///progetti/rettangoli)
error[E0507]: cannot move out of `valore`, a captured variable in an `FnMut` closure
--> src/main.rs:18:33
|
15 | let valore = String::from("chiusura chiamata");
| ------ --------------------------------- move occurs because `valore` has type `String`, which does not implement the `Copy` trait
| |
| captured outer variable
16 |
17 | lista.sort_by_key(|r| {
| --- captured by this `FnMut` closure
18 | azioni_ordinamento.push(valore);
| ^^^^^^ `valore` is moved here
|
help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but `FnOnce` closures may consume them only once
--> /rust/library/alloc/src/slice.rs:247:12
|
247 | F: FnMut(&T) -> K,
| ^^^^^^^^^^^^^^
help: consider cloning the value if the performance cost is acceptable
|
18 | azioni_ordinamento.push(valore.clone());
| ++++++++
For more information about this error, try `rustc --explain E0507`.
error: could not compile `rettangoli` (bin "rettangoli") due to 1 previous error
L’errore punta alla riga nel corpo della chiusura che sposta valore fuori
dall’ambiente. Per risolvere questo problema, dobbiamo modificare il corpo della
chiusura in modo che non sposti valori fuori dall’ambiente. Mantenere un
contatore nell’ambiente e incrementarne il valore nel corpo della chiusura è il
modo più semplice per contare il numero di volte in cui la chiusura viene
chiamata. La chiusura nel Listato 13-9 funziona con sort_by_key perché cattura
solo un reference mutabile al contatore numero_azioni_ordinamento e può
quindi essere chiamata più volte.
#[derive(Debug)] struct Rettangolo { larghezza: u32, altezza: u32, } fn main() { let mut lista = [ Rettangolo { larghezza: 10, altezza: 1 }, Rettangolo { larghezza: 3, altezza: 5 }, Rettangolo { larghezza: 7, altezza: 12 }, ]; let mut numero_azioni_ordinamento = 0; lista.sort_by_key(|r| { numero_azioni_ordinamento += 1; r.larghezza }); println!("{lista:#?}, ordinato in {numero_azioni_ordinamento} azioni"); }
FnMut con sort_by_keyI trait Fn sono importanti quando si definiscono o si utilizzano funzioni o
type che fanno uso di chiusure. Nella prossima sezione, parleremo degli
iteratori. Molti metodi iteratori accettano argomenti chiusura, quindi tieni a
mente questi dettagli sulle chiusure mentre proseguiamo!