Shopping cart

0

Cart

  • 0 item

Nessun prodotto nel carrello.

All categories

SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI

Corso Avanzato sulle Funzioni e le Chiusure

Questa sezione esplora alcune funzionalità avanzate relative alle funzioni e alle chiusure, inclusi i puntatori a funzione e il ritorno delle chiusure.

Puntatori a Funzione

Abbiamo parlato di come passare chiusure alle funzioni; è anche possibile passare funzioni regolari alle funzioni! Questa tecnica è utile quando si vuole passare una funzione che è già stata definita anziché definire una nuova chiusura. Le funzioni si convertono nel tipo fn (con una f minuscola), da non confondere con il trait di chiusura Fn. Il tipo fn è chiamato puntatore a funzione. Passare funzioni con puntatori a funzione ti consentirà di utilizzare funzioni come argomenti per altre funzioni.

La sintassi per specificare che un parametro è un puntatore a funzione è simile a quella delle chiusure, come mostrato nel Listato 19-27, dove abbiamo definito una funzione add_one che aggiunge uno al suo parametro. La funzione do_twice prende due parametri: un puntatore a funzione a qualsiasi funzione che prende un parametro di tipo i32 e restituisce un i32, e un valore i32. La funzione do_twice chiama la funzione f due volte, passandole il valore arg, quindi aggiunge insieme i risultati delle due chiamate di funzione. La funzione principale chiama do_twice con gli argomenti add_one e 5.

rust

fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
} fn main() {
let answer = do_twice(add_one, 5);

println!("La risposta è: {}", answer);
}

Listato 19-27: Utilizzo del tipo fn per accettare un puntatore a funzione come argomento

Questo codice stampa “La risposta è: 12”. Specifichiamo che il parametro f in do_twice è un fn che prende un parametro di tipo i32 e restituisce un i32. Possiamo quindi chiamare f nel corpo di do_twice. In main, possiamo passare il nome della funzione add_one come primo argomento a do_twice.

A differenza delle chiusure, fn è un tipo anziché un trait, quindi specifichiamo fn come tipo di parametro direttamente anziché dichiarare un parametro di tipo generico con uno dei trait Fn come vincolo del trait.

I puntatori a funzione implementano tutti e tre i trait di chiusura (Fn, FnMut e FnOnce), il che significa che puoi sempre passare un puntatore a funzione come argomento per una funzione che si aspetta una chiusura. È meglio scrivere le funzioni utilizzando un tipo generico e uno dei trait di chiusura in modo che le tue funzioni possano accettare sia funzioni che chiusure.

Detto questo, un esempio di dove vorresti accettare solo fn e non chiusure è quando interfacci con codice esterno che non ha chiusure: le funzioni C possono accettare funzioni come argomenti, ma C non ha chiusure.

Come esempio di dove potresti utilizzare una chiusura definita inline o una funzione nominata, guardiamo a un uso del metodo map fornito dal trait Iterator nella libreria standard. Per utilizzare la funzione map per trasformare un vettore di numeri in un vettore di stringhe, potremmo utilizzare una chiusura, così:

rust

let lista_di_numeri = vec![1, 2, 3];
let lista_di_stringhe: Vec<String> =
lista_di_numeri.iter().map(|i| i.to_string()).collect();

Oppure potremmo nominare una funzione come argomento per map invece della chiusura, così:

rust

let lista_di_numeri = vec![1, 2, 3];
let lista_di_stringhe: Vec<String> =
lista_di_numeri.iter().map(ToString::to_string).collect();

Nota che dobbiamo usare la sintassi completamente qualificata di cui abbiamo parlato in precedenza nella sezione “Traits Avanzati” perché ci sono più funzioni disponibili chiamate to_string. Qui, stiamo utilizzando la funzione to_string definita nel trait ToString, che la libreria standard ha implementato per qualsiasi tipo che implementa Display.

Ricorda dalla sezione “Valori di Enumerazione” del Capitolo 6 che il nome di ciascuna variante di enum che definiamo diventa anche una funzione inizializzatrice. Possiamo utilizzare queste funzioni inizializzatrici come puntatori a funzione che implementano i trait di chiusura, il che significa che possiamo specificare le funzioni inizializzatrici come argomenti per i metodi che prendono chiusure, così:

rust

enum Stato {
Valore(u32),
Stop,
}

let lista_di_stati: Vec<Stato> = (0u32..20).map(Stato::Valore).collect();

Qui creiamo istanze di Status::Value utilizzando ogni valore u32 nell’intervallo su cui viene chiamato map utilizzando la funzione inizializzatrice di Status::Value. Alcune persone preferiscono questo stile, e alcune persone preferiscono utilizzare chiusure. Compilano lo stesso codice, quindi usa lo stile che ti è più chiaro.

Ritorno delle Chiusure

Le chiusure sono rappresentate da trait, il che significa che non puoi restituire chiusure direttamente. Nella maggior parte dei casi in cui potresti voler restituire un trait, puoi invece utilizzare il tipo concreto che implementa il trait come valore di ritorno della funzione. Tuttavia, non puoi farlo con le chiusure perché non hanno un tipo concreto che è restituibile; non ti è permesso utilizzare il puntatore a funzione fn come tipo di ritorno, ad esempio.

Il seguente codice prova a restituire direttamente una chiusura, ma non compilerà:

rust

fn ritorna_chiusura() -> dyn Fn(i32) -> i32 {
|x| x + 1
}

L’errore del compilatore è il seguente:

rust

$ cargo build
Compilazione di functions-example v0.1.0 (file:///projects/functions-example)
errore[E0746]: il tipo di ritorno non può avere un oggetto trait unboxed
--> src/lib.rs:1:25
|
1 | fn ritorna_chiusura() -> dyn Fn(i32) -> i32 {
| ^^^^^^^^^^^^^^^^^^^ non ha una dimensione nota a tempo di compilazione
|
= nota: per informazioni su `impl Trait`, vedere <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
aiuto: usa `impl Fn(i32) -> i32` come tipo di ritorno, poiché tutti i percorsi di ritorno sono di tipo `[closure@src/lib.rs:2:5: 2:8]`, che implementa `Fn(i32) -> i32`
|
1 | fn ritorna_chiusura() -> impl Fn(i32) -> i32 {
| ~~~~~~~~~~~~~~~~~~~~

Per ulteriori informazioni su questo errore, prova `rustc --explain E0746`.
errore: impossibile compilare `functions-example` a causa di un errore precedente

L’errore fa riferimento nuovamente al trait Sized! Rust non sa di quanto spazio avrà bisogno per memorizzare la chiusura. Abbiamo visto una soluzione a questo problema in precedenza. Possiamo utilizzare un oggetto trait:

rust

fn ritorna_chiusura() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}

Questo codice verrà compilato senza problemi. Per ulteriori informazioni sugli oggetti trait, fare riferimento alla sezione “Utilizzo degli Oggetti Trait che Consentono Valori di Tipi Diversi” nel Capitolo 17.

Successivamente, diamo uno sguardo alle macro!

1 Comment

Leave a Reply

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *