00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Funzioni in Rust

Le funzioni sono i blocchi fondamentali per organizzare il codice in Rust. Ogni programma Rust inizia con la funzione main, ma è possibile definire quante funzioni si desidera. Rust adotta un approccio basato su espressioni: l’ultima espressione di una funzione ne diventa automaticamente il valore di ritorno, senza bisogno della parola chiave return.

Dichiarazione con fn

Le funzioni si dichiarano con la parola chiave fn, seguita dal nome, le parentesi per i parametri e il corpo della funzione.

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

fn main() {
    saluta();
    saluta();
}

In Rust, la convenzione per i nomi delle funzioni è lo snake_case: parole minuscole separate da underscore.

fn calcola_area_rettangolo() {
    let base = 10.0;
    let altezza = 5.0;
    println!("Area: {}", base * altezza);
}

Parametri con annotazione di tipo

I parametri delle funzioni devono sempre avere il tipo annotato esplicitamente. Rust non fa inferenza di tipo nelle firme delle funzioni.

fn saluta_persona(nome: &str) {
    println!("Ciao, {nome}!");
}

fn somma(a: i32, b: i32) {
    println!("{a} + {b} = {}", a + b);
}

fn main() {
    saluta_persona("Marco");
    somma(3, 7);
}

Ogni parametro è separato da una virgola e ha la forma nome: Tipo.

Tipi di ritorno con ->

Per indicare che una funzione restituisce un valore, si aggiunge -> seguito dal tipo dopo le parentesi dei parametri.

fn quadrato(n: i32) -> i32 {
    n * n
}

fn main() {
    let risultato = quadrato(5);
    println!("5 al quadrato = {risultato}");
}

Le funzioni possono restituire qualsiasi tipo:

fn e_pari(n: i32) -> bool {
    n % 2 == 0
}

fn messaggio_benvenuto(nome: &str) -> String {
    format!("Benvenuto, {}!", nome)
}

fn main() {
    println!("{}", e_pari(4));        // true
    println!("{}", e_pari(7));        // false
    println!("{}", messaggio_benvenuto("Luca"));
}

Ritorno basato su espressioni

In Rust, l’ultima espressione in un blocco (senza punto e virgola) diventa il valore di ritorno. Non serve return esplicito.

fn somma(a: i32, b: i32) -> i32 {
    a + b  // Niente punto e virgola: questa è un'espressione che viene restituita
}

fn valore_assoluto(n: i32) -> i32 {
    if n >= 0 {
        n
    } else {
        -n
    }
}

fn main() {
    println!("Somma: {}", somma(10, 20));
    println!("Valore assoluto di -5: {}", valore_assoluto(-5));
}

Attenzione: aggiungere un punto e virgola dopo l’ultima espressione la trasforma in un’istruzione, che non restituisce un valore. Questo causerebbe un errore di compilazione se la funzione dichiara un tipo di ritorno.

// Questo NON compila:
// fn somma(a: i32, b: i32) -> i32 {
//     a + b;  // Il punto e virgola rende questa un'istruzione, non un'espressione
// }

Return anticipato

La parola chiave return è usata per uscire anticipatamente da una funzione, restituendo un valore prima della fine del corpo.

fn dividi(dividendo: f64, divisore: f64) -> Option<f64> {
    if divisore == 0.0 {
        return None;  // Uscita anticipata
    }
    Some(dividendo / divisore)  // Valore di ritorno normale
}

fn main() {
    match dividi(10.0, 3.0) {
        Some(r) => println!("Risultato: {r:.2}"),
        None => println!("Divisione per zero!"),
    }
}

return è utile per le guard clause, cioè controlli iniziali che escono dalla funzione in caso di condizioni speciali:

fn processa_input(valore: i32) -> String {
    if valore < 0 {
        return String::from("Errore: valore negativo");
    }

    if valore > 1000 {
        return String::from("Errore: valore troppo grande");
    }

    format!("Valore processato: {}", valore * 2)
}

fn main() {
    println!("{}", processa_input(-5));
    println!("{}", processa_input(42));
    println!("{}", processa_input(5000));
}

Firme delle funzioni

La firma di una funzione comprende il nome, i parametri con i loro tipi e il tipo di ritorno. La firma è il contratto pubblico della funzione.

// Firma: fn nome(param1: Tipo1, param2: Tipo2) -> TipoRitorno

fn calcola_media(valori: &[f64]) -> f64 {
    if valori.is_empty() {
        return 0.0;
    }
    let somma: f64 = valori.iter().sum();
    somma / valori.len() as f64
}

fn main() {
    let voti = [7.5, 8.0, 6.5, 9.0, 7.0];
    println!("Media: {:.2}", calcola_media(&voti));
}

Il tipo Unit ()

Quando una funzione non restituisce esplicitamente un valore, il suo tipo di ritorno è il tipo unit (). È l’equivalente di void in C o Java, ma in Rust () è un valore reale (una tupla vuota).

fn stampa_linea() {
    println!("-------------------");
}

// Le due firme seguenti sono equivalenti:
fn fai_qualcosa() {
    println!("Fatto");
}

fn fai_qualcosa_esplicito() -> () {
    println!("Fatto");
}

fn main() {
    stampa_linea();
    let risultato = fai_qualcosa();
    println!("Il tipo unit: {:?}", risultato); // Stampa: Il tipo unit: ()
}

Funzioni come valori

In Rust, le funzioni possono essere passate come valori. Ogni funzione ha un suo tipo unico, ma può essere convertita in un puntatore a funzione (fn).

fn applica(f: fn(i32) -> i32, valore: i32) -> i32 {
    f(valore)
}

fn raddoppia(n: i32) -> i32 {
    n * 2
}

fn incrementa(n: i32) -> i32 {
    n + 1
}

fn main() {
    println!("Raddoppia 5: {}", applica(raddoppia, 5));
    println!("Incrementa 5: {}", applica(incrementa, 5));
}

Conclusione

Le funzioni in Rust sono potenti e sicure grazie al sistema di tipi rigoroso e all’approccio basato su espressioni. La firma esplicita dei tipi rende il codice autodocumentante, mentre il ritorno implicito dell’ultima espressione riduce la verbosita. Ricorda che return è necessario solo per uscite anticipate; per il ritorno normale, basta omettere il punto e virgola nell’ultima espressione.