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.