Shopping cart

0

Cart

  • 0 item

Nessun prodotto nel carrello.

All categories
 Corso Gratuito di Programmazione Rust Lezione 010 – Funzioni

SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI

Funzioni

Le funzioni sono molto diffuse nel codice Rust. Hai già visto una delle funzioni più importanti del linguaggio: la funzione main, che è il punto di ingresso di molti programmi. Hai anche visto la parola chiave fn, che ti permette di dichiarare nuove funzioni.

Il codice Rust utilizza lo snake case come stile convenzionale per i nomi di funzioni e variabili, in cui tutte le lettere sono in minuscolo e gli underscore separano le parole. Ecco un programma che contiene un esempio di definizione di funzione:

Nome file: src/main.rs

rust

fn main() {
println!("Ciao, mondo!");
altra_funzione();
}

fn altra_funzione() {
println!("Un'altra funzione.");
}

Definiamo una funzione in Rust inserendo fn seguito da un nome di funzione e un insieme di parentesi. Le parentesi graffe indicano al compilatore dove inizia e termina il corpo della funzione.

Possiamo chiamare qualsiasi funzione che abbiamo definito inserendo il suo nome seguito da un insieme di parentesi. Poiché altra_funzione è definita nel programma, può essere chiamata dall’interno della funzione main. Nota che abbiamo definito altra_funzione dopo la funzione main nel codice sorgente; avremmo potuto definirla anche prima. A Rust non importa dove si definiscono le funzioni, solo che siano definite da qualche parte in uno scope che può essere visto dal chiamante.

Iniziamo un nuovo progetto binario chiamato funzioni per esplorare ulteriormente le funzioni. Metti l’esempio di altra_funzione in src/main.rs e eseguilo. Dovresti vedere il seguente output:

less

$ cargo run
Compilazione di funzioni v0.1.0 (file:///projects/funzioni)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/funzioni`
Ciao, mondo!
Un'altra funzione.

Le righe vengono eseguite nell’ordine in cui appaiono nella funzione main. Prima viene stampato il messaggio “Ciao, mondo!”, e poi viene chiamata altra_funzione e viene stampato il suo messaggio. Parametri

Possiamo definire le funzioni in modo che abbiano dei parametri, che sono variabili speciali che fanno parte della firma di una funzione. Quando una funzione ha dei parametri, puoi fornire dei valori concreti per quei parametri. Tecnicamente, i valori concreti sono chiamati argomenti, ma nella conversazione informale le persone tendono a usare le parole parametro e argomento in modo intercambiabile sia per le variabili nella definizione di una funzione che per i valori concreti passati quando si chiama una funzione.

In questa versione di altra_funzione aggiungiamo un parametro:

Nome file: src/main.rs

rust

fn main() {
altra_funzione(5);
}

fn altra_funzione(x: i32) {
println!("Il valore di x è: {x}");
}

Prova ad eseguire questo programma; dovresti ottenere il seguente output:

less

$ cargo run
Compilazione di funzioni v0.1.0 (file:///projects/funzioni)
Finished dev [unoptimized + debuginfo] target(s) in 1.21s
Running `target/debug/funzioni`
Il valore di x è: 5

La dichiarazione di altra_funzione ha un parametro chiamato x. Il tipo di x è specificato come i32. Quando passiamo 5 a altra_funzione, il macro println! mette 5 dove si trovavano le parentesi graffe contenenti x nella stringa di formato.

Nelle firme delle funzioni, è necessario dichiarare il tipo di ciascun parametro. Questa è una decisione deliberata nel design di Rust: richiedere annotazioni di tipo nelle definizioni delle funzioni significa che il compilatore quasi mai ha bisogno che tu le utilizzi altrove nel codice per capire quale tipo intendi. Il compilatore è inoltre in grado di fornire messaggi di errore più utili se sa quali tipi la funzione si aspetta.

Quando si definiscono più parametri, separa le dichiarazioni dei parametri con virgole, così:

Nome file: src/main.rs

rust

fn main() {
stampa_misura_etichettata(5, 'h');
}

fn stampa_misura_etichettata(valore: i32, etichetta_unità: char) {
println!("La misura è: {valore}{etichetta_unità}");
}

Questo esempio crea una funzione chiamata stampa_misura_etichettata con due parametri. Il primo parametro si chiama valore ed è un i32. Il secondo si chiama etichetta_unità ed è di tipo char. La funzione quindi stampa un testo contenente sia il valore che l’etichetta_unità.

Proviamo a eseguire questo codice. Sostituisci il programma attualmente presente nel file src/main.rs del tuo progetto funzioni con l’esempio precedente e eseguilo usando cargo run:

less

$ cargo run
Compilazione di funzioni v0.1.0 (file:///projects/funzioni)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/funzioni`
La misura è: 5h

Poiché abbiamo chiamato la funzione con 5 come valore per valore e ‘h’ come valore per etichetta_unità, l’output del programma contiene quei valori. Istruzioni ed Espressioni

I corpi delle funzioni sono composti da una serie di istruzioni che terminano opzionalmente con un’espressione. Finora, le funzioni che abbiamo coperto non hanno incluso un’espressione di fine, ma hai visto un’espressione come parte di un’istruzione. Poiché Rust è un linguaggio basato su espressioni, questa è una distinzione importante da capire. Altri linguaggi non hanno le stesse distinzioni, quindi vediamo cosa sono le istruzioni e le espressioni e come le loro differenze influenzano i corpi delle funzioni.

  • Le istruzioni sono istruzioni che eseguono un’azione e non restituiscono un valore.
  • Le espressioni valutano a un valore risultante. Vediamo alcuni esempi.

In realtà, abbiamo già usato istruzioni ed espressioni. Creare una variabile e assegnare un valore ad essa con la parola chiave let è un’istruzione. Nell’Elenco 3-1, let y = 6; è un’istruzione.

Nome file: src/main.rs

rust

fn main() {
let y = 6;
}

Elenco 3-1: Una dichiarazione di funzione main contenente un’istruzione

Le definizioni di funzione sono anche istruzioni; l’intero esempio precedente è un’istruzione di per sé.

Le istruzioni non restituiscono valori. Pertanto, non puoi assegnare un’istruzione let a un’altra variabile, come prova a fare il seguente codice; otterrai un errore:

Nome file: src/main.rs

[Questo codice non compila!]

rust

fn main() {
let x = (let y = 6);
}

Quando esegui questo programma, l’errore che otterrai sarà simile a questo:

less

$ cargo run
Compilazione di funzioni v0.1.0 (file:///projects/funzioni)
error: expected expression, found `let` statement
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^
error: expected expression, found statement (`let`)
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: la dichiarazione di variabile usando `let` è un'istruzione error[E0658]: `let` expressions in this position are unstable
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^^^^^^^
|
= note: vedi l'issue #53667 <https://github.com/rust-lang/rust/issues/53667> per ulteriori informazioni warning: unnecessary parentheses around assigned value
--> src/main.rs:2:13
|
2 | let x = (let y = 6);
| ^ ^
|
= note: `#[warn(unused_parens)]` è abilitato per default
help: remove these parentheses
|
2 - let x = (let y = 6);
2 + let x = let y = 6;
|

Per ulteriori informazioni su questo errore, prova `rustc --explain E0658`.
warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` due to 3 previous errors; 1 warning emitted

La dichiarazione let y = 6 non restituisce un valore, quindi non c’è niente a cui x può essere associato. Questo è diverso da ciò che accade in altri linguaggi, come C e Ruby, dove l’assegnazione restituisce il valore dell’assegnazione. In quei linguaggi, puoi scrivere x = y = 6 e avere sia x che y con il valore 6; questo non è il caso in Rust.

Le espressioni valutano a un valore e costituiscono la maggior parte del resto del codice che scriverai in Rust. Considera un’operazione matematica, come 5 + 6, che è un’espressione che si valuta al valore 11. Le espressioni possono far parte delle istruzioni: nell’Elenco 3-1, il 6 nell’istruzione let y = 6; è un’espressione che si valuta al valore 6. Chiamare una funzione è un’espressione. Chiamare un macro è un’espressione. Un nuovo blocco di scope creato con parentesi graffe è un’espressione, ad esempio:

Nome file: src/main.rs

rust

fn main() {
let y = {
let x = 3;
x + 1
};

println!("Il valore di y è: {y}");
}

Questa espressione:

rust

{
let x = 3;
x + 1
}

è un blocco che, in questo caso, si valuta a 4. Quel valore viene legato a y come parte dell’istruzione let. Nota che la linea x + 1 non ha un punto e virgola alla fine, a differenza della maggior parte delle righe che hai visto finora. Le espressioni non includono punti e virgola finali. Se aggiungi un punto e virgola alla fine di un’espressione, la trasformi in un’istruzione, e non restituirà quindi un valore. Tieni presente questo mentre esplori i valori di ritorno delle funzioni ed espressioni dopo. Funzioni con Valori di Ritorno

Le funzioni possono restituire valori al codice che le chiama. Non nominiamo i valori di ritorno, ma dobbiamo dichiararne il tipo dopo una freccia (->). In Rust, il valore di ritorno della funzione è sinonimo del valore dell’espressione finale nel blocco del corpo di una funzione. Puoi ritornare presto da una funzione usando la parola chiave return e specificando un valore, ma la maggior parte delle funzioni restituisce implicitamente l’ultima espressione. Ecco un esempio di una funzione che restituisce un valore:

Nome file: src/main.rs

rust

fn cinque() -> i32 {
5
}
fn main() {
let x = cinque();

println!("Il valore di x è: {x}");
}

Non ci sono chiamate di funzione, macro o persino istruzioni let nella funzione cinque—solo il numero 5 da solo. Questa è una funzione perfettamente valida in Rust. Nota che il tipo di ritorno della funzione è specificato anche, come -> i32. Prova a eseguire questo codice; l’output dovrebbe assomigliare a questo:

less

$ cargo run
Compilazione di funzioni v0.1.0 (file:///projects/funzioni)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/funzioni`
Il valore di x è: 5

Il 5 in cinque è il valore di ritorno della funzione, motivo per cui il tipo di ritorno è i32. Esaminiamolo più nel dettaglio. Ci sono due bit importanti: innanzitutto, la linea let x = cinque(); mostra che stiamo usando il valore di ritorno di una funzione per inizializzare una variabile. Poiché la funzione cinque restituisce un 5, quella riga è la stessa di quanto segue:

let x = 5;

In secondo luogo, la funzione cinque non ha parametri e definisce il tipo del valore di ritorno, ma il corpo della funzione è un solitario 5 senza punto e virgola perché è un’espressione il cui valore vogliamo restituire.

Guardiamo un altro esempio:

Nome file: src/main.rs

rust

fn main() {
let x = più_uno(5);
println!("Il valore di x è: {x}");
}

fn più_uno(x: i32) -> i32 {
x + 1
}

Eseguendo questo codice verrà stampato Il valore di x è: 6. Ma se mettiamo un punto e virgola alla fine della riga che contiene x + 1, trasformandola da un’espressione in un’istruzione, otterremo un errore:

Nome file: src/main.rs

[Questo codice non compila!]

rust

fn main() {
let x = più_uno(5);
println!("Il valore di x è: {x}");
}

fn più_uno(x: i32) -> i32 {
x + 1;
}

Compilando questo codice produrrà un errore, come segue:

rust

$ cargo run
Compilazione di funzioni v0.1.0 (file:///projects/funzioni)
error[E0308]: mismatched types
--> src/main.rs:7:24
|
7 | fn più_uno(x: i32) -> i32 {
| -------- ^^^ expected `i32`, found `()`
| |
| restituisce implicitamente `()` poiché il suo corpo non ha una coda o un'espressione di `return`
8 | x + 1;
| - help: remove this semicolon to return this value

Per ulteriori informazioni su questo errore, prova `rustc --explain E0308`.
error: could not compile `functions` due to previous error

Il messaggio di errore principale, tipi non corrispondenti, rivela il problema principale di questo codice. La definizione della funzione più_uno dice che restituirà un i32, ma le istruzioni non si valutano a un valore, il che è espresso da (), il tipo unit. Di conseguenza, non viene restituito nulla, il che contraddice la definizione della funzione e porta a un errore. In questa uscita, Rust fornisce un messaggio per aiutare eventualmente a risolvere questo problema: suggerisce di rimuovere il punto e virgola, che correggerebbe l’errore.

1 Comment

Leave a Reply

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