saverioriotto.it

Tipi Avanzati in Rust: Strutture, Enum e Pattern Matching

Scopri i tipi avanzati in Rust: strutture (struct), enum, pattern matching con match e l'uso di Option e Result. Guida pratica con esempi chiari per padroneggiare Rust.

Tipi Avanzati in Rust: Strutture, Enum e Pattern Matching

Rust offre tipi avanzati che permettono di modellare dati complessi in modo elegante e sicuro. Le struct sono perfette per raggruppare dati correlati, mentre gli enum consentono di rappresentare varianti e stati. Con pattern matching, Rust fornisce un potente strumento per gestire questi tipi in modo chiaro ed espressivo. Infine, tipi speciali come Option e Result aiutano a gestire casi di valore mancante ed errori in modo sicuro.

In questa lezione, esploreremo questi concetti con esempi pratici.

Strutture (Struct) e Utilizzo

Le struct permettono di raggruppare più valori sotto un unico tipo. Ci sono tre tipi principali di struct in Rust:

  1. Struct Classica
  2. Struct a Tuple
  3. Struct Unitaria

Esempio: Struct Classica

Una struct classica ha campi nominati:

struct Persona {
    nome: String,
    eta: u32,
}

fn main() {
    let persona = Persona {
        nome: String::from("Alice"),
        eta: 30,
    };

    println!("Nome: {}, Età: {}", persona.nome, persona.eta);
}

Esempio: Struct a Tuple

Le struct a tuple non hanno nomi di campo, ma utilizzano posizioni:

struct Punto(i32, i32);

fn main() {
    let p = Punto(10, 20);
    println!("Punto: ({}, {})", p.0, p.1);
}

Struct Unitaria

Le struct unitarie non hanno campi. Vengono spesso utilizzate per implementare trait.

struct Unitaria;

fn main() {
    let _u = Unitaria;
}

Enum per Rappresentare Stati e Varianti

Gli enum in Rust sono ideali per rappresentare un insieme di varianti. Ogni variante può contenere dati aggiuntivi.

Esempio: Enum di Base

enum Stagione {
    Primavera,
    Estate,
    Autunno,
    Inverno,
}

fn main() {
    let stagione_corrente = Stagione::Estate;

    match stagione_corrente {
        Stagione::Primavera => println!("È primavera!"),
        Stagione::Estate => println!("È estate!"),
        Stagione::Autunno => println!("È autunno!"),
        Stagione::Inverno => println!("È inverno!"),
    }
}

Enum con Dati Aggiuntivi

Ogni variante può contenere dati:

enum Messaggio {
    Testo(String),
    Coordinata { x: i32, y: i32 },
    Numero(i32),
}

fn main() {
    let messaggio = Messaggio::Coordinata { x: 10, y: 20 };

    match messaggio {
        Messaggio::Testo(testo) => println!("Messaggio: {}", testo),
        Messaggio::Coordinata { x, y } => println!("Coordinate: ({}, {})", x, y),
        Messaggio::Numero(num) => println!("Numero: {}", num),
    }
}

Pattern Matching con match

Il pattern matching in Rust consente di verificare e destrutturare valori complessi in modo conciso e leggibile.

Esempio: Pattern Matching con Enum

enum Stato {
    Online,
    Offline,
    InRiparazione,
}

fn stato_dispositivo(stato: Stato) {
    match stato {
        Stato::Online => println!("Il dispositivo è online."),
        Stato::Offline => println!("Il dispositivo è offline."),
        Stato::InRiparazione => println!("Il dispositivo è in riparazione."),
    }
}

fn main() {
    let stato = Stato::Online;
    stato_dispositivo(stato);
}

Uso di _ per Gestire Casi Non Specificati

fn valuta(numero: i32) {
    match numero {
        1 => println!("Uno"),
        2 => println!("Due"),
        _ => println!("Altro numero"),
    }
}

fn main() {
    valuta(5);
}

Uso di Option e Result

Rust non utilizza null, ma fornisce i tipi Option e Result per rappresentare casi di valore mancante ed errori.

Option per Valori Mancanti

Il tipo Option può essere Some(T) per un valore presente o None per un valore mancante.

fn trova_valore(numero: i32) -> Option {
    if numero > 0 {
        Some(numero * 2)
    } else {
        None
    }
}

fn main() {
    match trova_valore(5) {
        Some(valore) => println!("Valore trovato: {}", valore),
        None => println!("Nessun valore trovato."),
    }
}

Result per Gestire Errori

Result è utilizzato per operazioni che possono fallire. Può essere Ok(T) per un risultato valido o Err(E) per un errore.

fn dividi(a: i32, b: i32) -> Result {
    if b == 0 {
        Err(String::from("Errore: divisione per zero"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    match dividi(10, 0) {
        Ok(risultato) => println!("Risultato: {}", risultato),
        Err(errore) => println!("{}", errore),
    }
}

Esercizi Pratici

  1. Esercizio 1: Crea una struct Rettangolo con campi altezza e larghezza, e implementa una funzione per calcolare l’area.
  2. Esercizio 2: Definisci un enum Operazione con varianti per addizione, sottrazione e moltiplicazione, e utilizza match per eseguire l'operazione corrispondente.
  3. Esercizio 3: Usa Option per scrivere una funzione che restituisce il quadrato di un numero positivo o None se il numero è negativo.
  4. Esercizio 4: Scrivi una funzione che utilizza Result per leggere un file e gestire possibili errori.

Conclusione

Le struct e gli enum in Rust ti permettono di modellare i dati in modo preciso ed espressivo, mentre pattern matching e tipi come Option e Result garantiscono un controllo efficace sui flussi di dati e sugli errori. Questi strumenti, insieme, ti aiuteranno a scrivere codice robusto, sicuro e facilmente manutenibile. Nel prossimo articolo esploreremo argomenti come Vettori, Array e Iteratori. Continua a esercitarti con gli esempi forniti e padroneggia questi concetti avanzati di Rust!




Commenti
* Obbligatorio