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

Strutture in Rust

Le strutture (struct) sono uno dei principali strumenti in Rust per creare tipi di dato personalizzati. Permettono di raggruppare campi con nomi e tipi diversi in un’unica entita, rendendo il codice piu leggibile e organizzato.

Definire una Struct

Una struct si definisce con la keyword struct, seguita dal nome (in PascalCase) e dai campi tra parentesi graffe.

struct Utente {
    nome: String,
    email: String,
    eta: u32,
    attivo: bool,
}

Ogni campo ha un nome e un tipo. La struct stessa non occupa memoria finche non viene creata un’istanza.

Creare Istanze

Per creare un’istanza di una struct, si specificano i valori di tutti i campi:

fn main() {
    let utente1 = Utente {
        nome: String::from("Marco"),
        email: String::from("marco@email.it"),
        eta: 28,
        attivo: true,
    };

    println!("Utente: {}", utente1.nome);
}

Se il nome della variabile coincide con il nome del campo, si puo usare la sintassi abbreviata:

fn crea_utente(nome: String, email: String) -> Utente {
    Utente {
        nome,     // equivale a nome: nome
        email,    // equivale a email: email
        eta: 0,
        attivo: true,
    }
}

Accesso ai Campi

Si accede ai campi di una struct con la notazione a punto:

fn main() {
    let utente = Utente {
        nome: String::from("Laura"),
        email: String::from("laura@email.it"),
        eta: 32,
        attivo: true,
    };

    println!("Nome: {}", utente.nome);
    println!("Eta: {}", utente.eta);
    println!("Attiva: {}", utente.attivo);
}

Istanze Mutabili

Per modificare i campi di una struct, l’intera istanza deve essere dichiarata come mut. Rust non permette di rendere mutabili solo alcuni campi.

fn main() {
    let mut utente = Utente {
        nome: String::from("Paolo"),
        email: String::from("paolo@email.it"),
        eta: 25,
        attivo: false,
    };

    utente.attivo = true;
    utente.eta = 26;

    println!("{} ha {} anni, attivo: {}", utente.nome, utente.eta, utente.attivo);
}

Sintassi di Aggiornamento (…)

La sintassi di aggiornamento .. permette di creare una nuova istanza partendo da un’altra, sovrascrivendo solo alcuni campi. I campi non specificati vengono copiati (o spostati) dall’istanza originale.

fn main() {
    let utente1 = Utente {
        nome: String::from("Anna"),
        email: String::from("anna@email.it"),
        eta: 30,
        attivo: true,
    };

    let utente2 = Utente {
        email: String::from("anna.nuova@email.it"),
        ..utente1
    };

    // Attenzione: utente1.nome e stato spostato in utente2
    // utente1.eta e utente1.attivo sono ancora accessibili (implementano Copy)
    println!("Utente2: {} - {}", utente2.nome, utente2.email);
}

Tuple Struct

Le tuple struct sono struct senza nomi per i campi. Sono utili quando si vuole dare un nome a una tupla per distinguerla semanticamente.

struct Colore(u8, u8, u8);
struct Punto3D(f64, f64, f64);

fn main() {
    let rosso = Colore(255, 0, 0);
    let origine = Punto3D(0.0, 0.0, 0.0);

    println!("Rosso: ({}, {}, {})", rosso.0, rosso.1, rosso.2);
    println!("Origine: ({}, {}, {})", origine.0, origine.1, origine.2);

    // Destrutturazione
    let Colore(r, g, b) = rosso;
    println!("R={}, G={}, B={}", r, g, b);
}

Nota che Colore e Punto3D sono tipi diversi anche se hanno la stessa struttura interna: non sono intercambiabili.

Unit-Like Struct

Le unit-like struct non hanno alcun campo. Sono utili quando si vuole implementare un trait su un tipo senza memorizzare dati.

struct Marker;

// Utile per implementare trait senza dati
impl std::fmt::Display for Marker {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "Marker")
    }
}

fn main() {
    let m = Marker;
    println!("{}", m);
}

Stampare Struct con Debug

Per stampare una struct con println!, occorre derivare il trait Debug. Questo si fa con l’attributo #[derive(Debug)].

#[derive(Debug)]
struct Rettangolo {
    larghezza: f64,
    altezza: f64,
}

fn main() {
    let rett = Rettangolo {
        larghezza: 10.0,
        altezza: 5.0,
    };

    // Formato Debug standard
    println!("Rettangolo: {:?}", rett);

    // Formato Debug formattato (pretty-print)
    println!("Rettangolo:\n{:#?}", rett);

    // Utile per il debugging con dbg! macro
    let area = dbg!(rett.larghezza * rett.altezza);
    println!("Area: {}", area);
}

Il macro dbg! e particolarmente utile durante lo sviluppo: stampa il file, la riga e il valore dell’espressione su stderr.

Struct con Lifetime

Quando una struct contiene riferimenti, e necessario annotare le lifetime per garantire che i riferimenti siano validi per tutta la durata della struct.

#[derive(Debug)]
struct Estratto<'a> {
    testo: &'a str,
}

fn main() {
    let romanzo = String::from("Era una notte buia e tempestosa...");
    let estratto = Estratto {
        testo: &romanzo[..20],
    };
    println!("Estratto: {:?}", estratto);
}

Conclusione

Le struct in Rust sono lo strumento fondamentale per definire tipi personalizzati. Tra struct classiche con campi nominati, tuple struct e unit-like struct, Rust offre grande flessibilita nella modellazione dei dati. La sintassi di aggiornamento con .. semplifica la creazione di varianti, mentre #[derive(Debug)] facilita il debugging durante lo sviluppo. Combinando le struct con i metodi e i trait, si ottiene un sistema di tipi espressivo e sicuro.