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

Option in Rust

Il tipo Option<T> e il modo in cui Rust gestisce i valori che possono essere presenti o assenti. A differenza di molti linguaggi che usano null o nil, Rust rende esplicita l’assenza di un valore attraverso il sistema dei tipi, eliminando un’intera classe di bug a tempo di compilazione.

Some(T) e None

Option<T> e un enum definito nella libreria standard con due varianti:

// Definizione nella libreria standard (non serve riscriverla)
// enum Option<T> {
//     Some(T),
//     None,
// }

fn main() {
    let numero: Option<i32> = Some(42);
    let niente: Option<i32> = None;

    println!("Numero: {:?}", numero);  // Some(42)
    println!("Niente: {:?}", niente);  // None

    // Option e cosi comune che Some e None sono nel prelude
    // Non serve scrivere Option::Some o Option::None
    let nome = Some("Rust");
    let vuoto: Option<&str> = None;
}

Perche Rust Non Ha null

In molti linguaggi, null puo apparire ovunque, causando errori a runtime (NullPointerException). In Rust, un valore di tipo T e sempre valido. Se un valore puo essere assente, bisogna usare esplicitamente Option<T>, costringendo il programmatore a gestire il caso None.

fn trova_utente(id: u32) -> Option<String> {
    if id == 1 {
        Some(String::from("Marco"))
    } else {
        None
    }
}

fn main() {
    let utente = trova_utente(1);

    // Non puoi usare utente direttamente come String
    // Devi prima gestire il caso None
    // let nome: String = utente; // ERRORE di compilazione!

    // Modo corretto:
    match utente {
        Some(nome) => println!("Trovato: {}", nome),
        None => println!("Utente non trovato"),
    }
}

unwrap e expect

I metodi unwrap e expect estraggono il valore da un Some, ma causano un panic se il valore e None. Usali solo quando sei certo che il valore sia presente.

fn main() {
    let x: Option<i32> = Some(10);
    let y: Option<i32> = None;

    // unwrap: estrae il valore o va in panic
    println!("x = {}", x.unwrap()); // 10

    // expect: come unwrap ma con messaggio personalizzato
    // println!("y = {}", y.expect("Il valore doveva essere presente!"));
    // PANIC: Il valore doveva essere presente!

    // is_some e is_none per controlli sicuri
    println!("x ha un valore: {}", x.is_some()); // true
    println!("y e None: {}", y.is_none());         // true
}

Pattern Matching con Option

Il pattern matching e il modo piu esplicito e sicuro per gestire Option:

fn dividi(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(a / b)
    }
}

fn main() {
    let risultato = dividi(10.0, 3.0);

    match risultato {
        Some(valore) => println!("Risultato: {:.2}", valore),
        None => println!("Impossibile dividere per zero"),
    }

    // Con guard condition
    match dividi(100.0, 7.0) {
        Some(v) if v > 10.0 => println!("Risultato grande: {:.2}", v),
        Some(v) => println!("Risultato: {:.2}", v),
        None => println!("Errore"),
    }
}

Metodi Combinatori: map, and_then, unwrap_or

Rust offre metodi funzionali per trasformare e combinare Option senza match espliciti:

fn main() {
    let numero: Option<i32> = Some(5);
    let niente: Option<i32> = None;

    // map: trasforma il valore interno
    let doppio = numero.map(|n| n * 2);
    println!("Doppio: {:?}", doppio); // Some(10)
    println!("Doppio di niente: {:?}", niente.map(|n| n * 2)); // None

    // unwrap_or: fornisce un valore di default
    println!("Valore: {}", numero.unwrap_or(0));  // 5
    println!("Default: {}", niente.unwrap_or(0));  // 0

    // unwrap_or_else: default calcolato con closure (lazy)
    let val = niente.unwrap_or_else(|| {
        println!("Calcolo il default...");
        42
    });
    println!("Valore calcolato: {}", val);

    // and_then (flatmap): per operazioni che restituiscono Option
    let risultato = Some("42")
        .and_then(|s| s.parse::<i32>().ok())
        .map(|n| n * 2);
    println!("Risultato: {:?}", risultato); // Some(84)

    // filter: mantiene il valore solo se la condizione e vera
    let pari = Some(4).filter(|n| n % 2 == 0);
    let dispari = Some(3).filter(|n| n % 2 == 0);
    println!("Pari: {:?}, Dispari: {:?}", pari, dispari); // Some(4), None
}

L’Operatore ? con Option

L’operatore ? puo essere usato con Option in funzioni che restituiscono Option. Se il valore e None, la funzione restituisce None immediatamente.

#[derive(Debug)]
struct Indirizzo {
    citta: String,
}

#[derive(Debug)]
struct Utente {
    nome: String,
    indirizzo: Option<Indirizzo>,
}

fn citta_utente(utente: &Option<Utente>) -> Option<&str> {
    let utente = utente.as_ref()?;
    let indirizzo = utente.indirizzo.as_ref()?;
    Some(&indirizzo.citta)
}

fn main() {
    let utente = Some(Utente {
        nome: String::from("Anna"),
        indirizzo: Some(Indirizzo {
            citta: String::from("Milano"),
        }),
    });

    let senza_indirizzo = Some(Utente {
        nome: String::from("Luca"),
        indirizzo: None,
    });

    println!("Citta: {:?}", citta_utente(&utente));            // Some("Milano")
    println!("Citta: {:?}", citta_utente(&senza_indirizzo));    // None
    println!("Citta: {:?}", citta_utente(&None));               // None
}

if let e while let con Option

Per gestire solo il caso Some senza un match completo, si usano if let e while let:

fn main() {
    let configurazione: Option<&str> = Some("produzione");

    // if let: gestisce solo Some
    if let Some(env) = configurazione {
        println!("Ambiente: {}", env);
    } else {
        println!("Nessuna configurazione impostata");
    }

    // while let: itera finche il valore e Some
    let mut stack = vec![1, 2, 3, 4, 5];

    while let Some(valore) = stack.pop() {
        println!("Estratto: {}", valore);
    }
    println!("Stack vuoto!");
}

Concatenare Operazioni su Option

Un esempio pratico di concatenazione di operazioni:

fn primo_numero_pari(testo: &str) -> Option<i32> {
    testo
        .split_whitespace()
        .filter_map(|s| s.parse::<i32>().ok())
        .find(|n| n % 2 == 0)
}

fn main() {
    let testo = "ho 3 gatti e 4 cani e 7 pesci";
    match primo_numero_pari(testo) {
        Some(n) => println!("Primo numero pari trovato: {}", n),
        None => println!("Nessun numero pari trovato"),
    }
}

Conclusione

Option<T> e il pilastro della sicurezza contro i valori nulli in Rust. Usandolo correttamente, si eliminano i null pointer error a tempo di compilazione. I metodi combinatori come map, and_then, unwrap_or e l’operatore ? permettono di scrivere codice conciso ed espressivo senza sacrificare la sicurezza. La regola d’oro: evita unwrap() nel codice di produzione e preferisci sempre la gestione esplicita dei casi None.