Trait in Rust
I trait sono il meccanismo di Rust per definire comportamenti condivisi tra tipi diversi. Sono simili alle interfacce di altri linguaggi, ma piu potenti: supportano implementazioni di default, trait bounds e composizione. I trait sono alla base del polimorfismo in Rust.
Definire un Trait
Un trait definisce un insieme di metodi che un tipo deve implementare:
trait Animale {
fn nome(&self) -> &str;
fn verso(&self) -> String;
// Metodo che usa altri metodi del trait
fn presentati(&self) -> String {
format!("Sono {} e faccio {}", self.nome(), self.verso())
}
}
Implementare un Trait
Si implementa un trait per un tipo con impl Trait for Tipo:
struct Cane {
nome: String,
razza: String,
}
struct Gatto {
nome: String,
}
impl Animale for Cane {
fn nome(&self) -> &str {
&self.nome
}
fn verso(&self) -> String {
String::from("Bau bau!")
}
}
impl Animale for Gatto {
fn nome(&self) -> &str {
&self.nome
}
fn verso(&self) -> String {
String::from("Miao!")
}
}
fn main() {
let cane = Cane {
nome: String::from("Rex"),
razza: String::from("Pastore tedesco"),
};
let gatto = Gatto {
nome: String::from("Micio"),
};
println!("{}", cane.presentati());
println!("{}", gatto.presentati());
}
Implementazioni di Default
I trait possono fornire implementazioni di default che i tipi possono sovrascrivere:
trait Registratore {
fn livello(&self) -> &str {
"INFO"
}
fn prefisso(&self) -> String {
format!("[{}]", self.livello())
}
fn registra(&self, messaggio: &str) {
println!("{} {}", self.prefisso(), messaggio);
}
}
struct LogConsole;
struct LogErrore;
impl Registratore for LogConsole {
// Usa tutte le implementazioni di default
}
impl Registratore for LogErrore {
fn livello(&self) -> &str {
"ERRORE"
}
// prefisso e registra usano il default, ma con il nuovo livello
}
fn main() {
let console = LogConsole;
let errori = LogErrore;
console.registra("Applicazione avviata"); // [INFO] Applicazione avviata
errori.registra("Connessione persa"); // [ERRORE] Connessione persa
}
Trait come Parametri
Ci sono diversi modi per usare i trait come vincoli sui parametri delle funzioni:
trait Riassumibile {
fn riassunto(&self) -> String;
}
struct Articolo {
titolo: String,
autore: String,
contenuto: String,
}
impl Riassumibile for Articolo {
fn riassunto(&self) -> String {
format!("{} di {} - {}...", self.titolo, self.autore, &self.contenuto[..20.min(self.contenuto.len())])
}
}
// Sintassi impl Trait (la piu concisa)
fn stampa_riassunto(elemento: &impl Riassumibile) {
println!("Riassunto: {}", elemento.riassunto());
}
// Sintassi trait bound (piu esplicita)
fn stampa_riassunto_bound<T: Riassumibile>(elemento: &T) {
println!("Riassunto: {}", elemento.riassunto());
}
// Clausola where (per situazioni complesse)
fn confronta_riassunti<T, U>(a: &T, b: &U) -> bool
where
T: Riassumibile,
U: Riassumibile,
{
a.riassunto().len() > b.riassunto().len()
}
Trait Multipli con +
Si possono richiedere piu trait contemporaneamente:
use std::fmt;
fn stampa_e_clona<T: fmt::Display + Clone>(valore: &T) {
let copia = valore.clone();
println!("Originale: {}", valore);
println!("Copia: {}", copia);
}
// Con where per leggibilita
fn elabora<T>(elemento: &T) -> String
where
T: fmt::Display + fmt::Debug + Clone,
{
let copia = elemento.clone();
format!("Display: {} | Debug: {:?}", elemento, copia)
}
fn main() {
stampa_e_clona(&String::from("Ciao Rust"));
println!("{}", elabora(&42));
}
Restituire Trait con impl Trait
Si puo restituire un tipo che implementa un trait senza specificare il tipo concreto:
trait Saluto {
fn saluta(&self) -> String;
}
struct Italiano;
struct Inglese;
impl Saluto for Italiano {
fn saluta(&self) -> String {
String::from("Ciao!")
}
}
impl Saluto for Inglese {
fn saluta(&self) -> String {
String::from("Hello!")
}
}
fn crea_salutatore(lingua: &str) -> Box<dyn Saluto> {
match lingua {
"it" => Box::new(Italiano),
"en" => Box::new(Inglese),
_ => Box::new(Italiano),
}
}
// impl Trait nel tipo di ritorno (un solo tipo concreto)
fn salutatore_italiano() -> impl Saluto {
Italiano
}
fn main() {
let s = crea_salutatore("en");
println!("{}", s.saluta());
let s2 = salutatore_italiano();
println!("{}", s2.saluta());
}
Supertrait
Un trait puo richiedere che il tipo implementi anche altri trait:
use std::fmt;
// Chi implementa Stampabile deve implementare anche Display
trait Stampabile: fmt::Display {
fn stampa(&self) {
println!("{}", self);
}
fn stampa_maiuscolo(&self) {
println!("{}", self.to_string().to_uppercase());
}
}
struct Messaggio(String);
impl fmt::Display for Messaggio {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Stampabile for Messaggio {}
fn main() {
let msg = Messaggio(String::from("ciao mondo"));
msg.stampa(); // ciao mondo
msg.stampa_maiuscolo(); // CIAO MONDO
}
Trait Standard Comuni
La libreria standard di Rust definisce molti trait fondamentali:
// Display: formattazione leggibile per l'utente
// Debug: formattazione per il debugging
// Clone: duplicazione esplicita
// Copy: duplicazione implicita (tipi semplici)
// Default: valore predefinito
// PartialEq, Eq: uguaglianza
// Hash: hashing per HashMap e HashSet
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct Punto {
x: i32,
y: i32,
}
impl std::fmt::Display for Punto {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p1 = Punto { x: 3, y: 4 };
let p2 = p1.clone();
let p3 = Punto::default(); // (0, 0)
println!("Display: {}", p1);
println!("Debug: {:?}", p2);
println!("Uguali: {}", p1 == p2);
println!("Default: {}", p3);
// Hash permette l'uso in HashMap
use std::collections::HashMap;
let mut mappa = HashMap::new();
mappa.insert(p1, "punto uno");
println!("Mappa: {:?}", mappa);
}
Conclusione
I trait sono il cuore del sistema di astrazione di Rust. Permettono di definire comportamenti condivisi, vincolare i generics e implementare il polimorfismo senza ereditarieta. Con le implementazioni di default, i supertrait e la ricca collezione di trait standard (Display, Debug, Clone, Copy, Default, PartialEq, Hash), i trait offrono un sistema espressivo e sicuro per la programmazione generica. La regola d’oro: se un comportamento e condiviso tra piu tipi, definisci un trait.