Enum in Rust
Le enum (enumerazioni) sono uno dei tipi piu potenti di Rust. A differenza delle enum di molti altri linguaggi, le enum in Rust possono contenere dati associati a ciascuna variante, rendendole simili ai tipi algebrici (algebraic data types) dei linguaggi funzionali.
Definire un Enum
Un enum si definisce con la keyword enum e un elenco di varianti:
enum Direzione {
Nord,
Sud,
Est,
Ovest,
}
fn main() {
let dir = Direzione::Nord;
match dir {
Direzione::Nord => println!("Vai verso nord"),
Direzione::Sud => println!("Vai verso sud"),
Direzione::Est => println!("Vai verso est"),
Direzione::Ovest => println!("Vai verso ovest"),
}
}
Enum con Dati Associati
La vera potenza degli enum in Rust sta nella possibilita di associare dati a ciascuna variante. Esistono tre forme di variante:
enum Messaggio {
// Variante unitaria (senza dati)
Esci,
// Variante con dati posizionali (stile tupla)
Sposta(i32, i32),
// Variante con campi nominati (stile struct)
Scrivi { testo: String, urgente: bool },
// Variante con singolo dato
CambiaColore(String),
}
fn main() {
let msg1 = Messaggio::Esci;
let msg2 = Messaggio::Sposta(10, 20);
let msg3 = Messaggio::Scrivi {
testo: String::from("Ciao!"),
urgente: true,
};
let msg4 = Messaggio::CambiaColore(String::from("#FF0000"));
gestisci_messaggio(msg2);
gestisci_messaggio(msg3);
}
fn gestisci_messaggio(msg: Messaggio) {
match msg {
Messaggio::Esci => println!("Uscita dal programma"),
Messaggio::Sposta(x, y) => println!("Spostamento a ({}, {})", x, y),
Messaggio::Scrivi { testo, urgente } => {
if urgente {
println!("URGENTE: {}", testo);
} else {
println!("{}", testo);
}
}
Messaggio::CambiaColore(colore) => println!("Nuovo colore: {}", colore),
}
}
Matching degli Enum
Il costrutto match e il modo principale per gestire le varianti di un enum. Rust richiede che il match sia esaustivo: tutte le varianti devono essere coperte.
enum Moneta {
Centesimo,
DueCentesimi,
CinqueCentesimi,
DieciCentesimi,
VentiCentesimi,
CinquantaCentesimi,
Euro,
DueEuro,
}
fn valore_in_centesimi(moneta: &Moneta) -> u32 {
match moneta {
Moneta::Centesimo => 1,
Moneta::DueCentesimi => 2,
Moneta::CinqueCentesimi => 5,
Moneta::DieciCentesimi => 10,
Moneta::VentiCentesimi => 20,
Moneta::CinquantaCentesimi => 50,
Moneta::Euro => 100,
Moneta::DueEuro => 200,
}
}
fn main() {
let moneta = Moneta::CinquantaCentesimi;
println!("Valore: {} centesimi", valore_in_centesimi(&moneta));
}
Per gestire varianti non specifiche, si usa il pattern _ o una variabile catch-all:
fn descrivi_moneta(moneta: &Moneta) -> &str {
match moneta {
Moneta::Euro | Moneta::DueEuro => "moneta grande",
_ => "moneta piccola",
}
}
Metodi sugli Enum
Come per le struct, anche gli enum possono avere metodi definiti in blocchi impl:
#[derive(Debug)]
enum Forma {
Cerchio(f64),
Rettangolo(f64, f64),
Triangolo(f64, f64),
}
impl Forma {
fn area(&self) -> f64 {
match self {
Forma::Cerchio(raggio) => std::f64::consts::PI * raggio * raggio,
Forma::Rettangolo(base, altezza) => base * altezza,
Forma::Triangolo(base, altezza) => base * altezza / 2.0,
}
}
fn descrizione(&self) -> String {
match self {
Forma::Cerchio(r) => format!("Cerchio con raggio {}", r),
Forma::Rettangolo(b, h) => format!("Rettangolo {}x{}", b, h),
Forma::Triangolo(b, h) => format!("Triangolo base {} altezza {}", b, h),
}
}
fn e_cerchio(&self) -> bool {
matches!(self, Forma::Cerchio(_))
}
}
fn main() {
let forme: Vec<Forma> = vec![
Forma::Cerchio(5.0),
Forma::Rettangolo(4.0, 6.0),
Forma::Triangolo(3.0, 8.0),
];
for forma in &forme {
println!("{}: area = {:.2}", forma.descrizione(), forma.area());
}
let cerchi: Vec<&Forma> = forme.iter().filter(|f| f.e_cerchio()).collect();
println!("Numero di cerchi: {}", cerchi.len());
}
Enum della Libreria Standard
Rust include diversi enum fondamentali nella libreria standard:
fn main() {
// Option<T>: rappresenta un valore che puo essere presente o assente
let numero: Option<i32> = Some(42);
let niente: Option<i32> = None;
// Result<T, E>: rappresenta un'operazione che puo riuscire o fallire
let ok: Result<i32, String> = Ok(100);
let errore: Result<i32, String> = Err(String::from("errore"));
// Ordering: usato per i confronti
use std::cmp::Ordering;
let confronto: Ordering = 5.cmp(&3);
match confronto {
Ordering::Less => println!("Minore"),
Ordering::Equal => println!("Uguale"),
Ordering::Greater => println!("Maggiore"),
}
}
Enum con Valori Discriminanti
Come in C, si possono assegnare valori interi alle varianti:
#[derive(Debug)]
enum CodiceHTTP {
Ok = 200,
NonTrovato = 404,
ErroreServer = 500,
}
fn main() {
println!("OK: {}", CodiceHTTP::Ok as i32); // 200
println!("Non trovato: {}", CodiceHTTP::NonTrovato as i32); // 404
}
Conclusione
Gli enum in Rust vanno ben oltre le semplici enumerazioni di altri linguaggi. La possibilita di associare dati a ciascuna variante, combinata con il pattern matching esaustivo, permette di modellare stati e situazioni complesse in modo sicuro e leggibile. Gli enum sono alla base di molti pattern idiomatici di Rust, da Option e Result fino alle macchine a stati e ai comandi di un’applicazione.