È uscito il Corso Java Completo — usa il coupon JAVA2026 (fino al 30 giugno)

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.