Funzioni

Abbiamo già incontrato la funzione main, che viene invocata all'inizio dell'esecuzione di ogni programma. Possiamo definire altre funzioni tramite la parola chiave fn, e possono essere invocate da altre funzioni.

fn main() {
    println!("funzione main");

    // chiama funzione stampa
    stampa();
}

fn stampa() {
    println!("funzione stampa");
}

Se proviamo a eseguire questo programma ci viene restituito

funzione main
funzione stampa

Le funzioni possono prendere dei parametri in ingresso, in questo caso dobbiamo fornire i tipi dei parametri esplicitamente

fn main() {
    let x = 5;
    stampa_valore(x);
}

fn stampa_valore(valore: u32) {
    println!("Il valore è {valore}");
}

Il risultato di questo codice è

Il valore è 5

Valori di ritorno

Le funzioni possono ritornare dei valori, e il tipo di ritorno deve essere annotato esplicitamente con la notazione ->.

fn main() {
    let x = 5;
    let y = 6;
    let somma = calcola_somma(x, y);
    println!("La somma di {} e {} è {}", x, y, somma);
}

fn calcola_somma(x: i32, y: i32) -> i32 {
    x + y
}

Il risultato dell'esecuzione di questo codice è

La somma di 5 e 6 è 11

Questo codice potrebbe sembrare strano perché non ci sono istruzioni di tipo return. Di fatto, in Rust è presente questa istruzione e la funzione sopra è equivalente a questa:

#![allow(unused)]
fn main() {
fn calcola_somma(x: i32, y: i32) -> i32 {
    return x + y;
}
}

Ma sopra stiamo utilizzando il fatto che le funzioni in Rust ritornano implicitamente il valore dell'ultima espressione (senza il punto e virgola). Questa è una feature anche presente nei blocchi, per esempio

fn main() {
    let a = {
        let x = 4;
        x * 3
    };
    println!("a: {a}"); // => a: 12
}

Quello che sta accadendo è che stiamo definendo un nuovo blocco tra parentesi graffe, e il risultato della valutazione del blocco è implicitamente l'ultima espressione senza il punto e virgola finale. Le classiche regole di scoping vengono applicate, quindi al di fuori del blocco la variabile x non può essere acceduta. Vedremo nel prossimo capitolo che il passaggio di variabili da uno scope all'altro in Rust avviene in maniera particolare.