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

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.