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

Trait Objects in Rust

I trait objects in Rust permettono il polimorfismo dinamico: lavorare con tipi diversi che implementano lo stesso trait, decidendo il metodo da chiamare a runtime.

Dispatch Statico vs Dinamico

trait Animale {
    fn verso(&self) -> &str;
}

struct Cane;
struct Gatto;

impl Animale for Cane {
    fn verso(&self) -> &str { "Bau!" }
}

impl Animale for Gatto {
    fn verso(&self) -> &str { "Miao!" }
}

// Dispatch STATICO (generics) - risolto a compile-time
fn stampa_verso_statico(a: &impl Animale) {
    println!("{}", a.verso());
}

// Dispatch DINAMICO (trait object) - risolto a runtime
fn stampa_verso_dinamico(a: &dyn Animale) {
    println!("{}", a.verso());
}

Sintassi dyn Trait

I trait objects si creano con dyn NomeTrait e devono stare dietro un puntatore:

fn main() {
    let cane = Cane;
    let gatto = Gatto;

    // Riferimento a trait object
    let animale: &dyn Animale = &cane;
    println!("{}", animale.verso());

    // Box<dyn Trait> - ownership del trait object
    let animale: Box<dyn Animale> = Box::new(Gatto);
    println!("{}", animale.verso());
}

Vec di Trait Objects

Permette di avere una collezione di tipi diversi:

fn main() {
    let animali: Vec<Box<dyn Animale>> = vec![
        Box::new(Cane),
        Box::new(Gatto),
        Box::new(Cane),
    ];

    for animale in &animali {
        println!("{}", animale.verso());
    }
    // Bau! Miao! Bau!
}

Object Safety

Non tutti i trait possono diventare trait objects. Un trait è object-safe se:

  • I metodi non restituiscono Self
  • I metodi non hanno parametri di tipo generico
// Object-safe ✅
trait Disegnabile {
    fn disegna(&self);
    fn area(&self) -> f64;
}

// NON object-safe ❌
trait Clonabile {
    fn clona(&self) -> Self; // Restituisce Self
}

trait Comparabile {
    fn confronta<T>(&self, other: &T); // Parametro generico
}

Pattern: Strategia con Trait Objects

trait Formatter {
    fn formatta(&self, dati: &str) -> String;
}

struct FormatterJSON;
struct FormatterXML;

impl Formatter for FormatterJSON {
    fn formatta(&self, dati: &str) -> String {
        format!("{{\"dati\": \"{}\"}}", dati)
    }
}

impl Formatter for FormatterXML {
    fn formatta(&self, dati: &str) -> String {
        format!("<dati>{}</dati>", dati)
    }
}

fn esporta(formatter: &dyn Formatter, dati: &str) -> String {
    formatter.formatta(dati)
}

fn main() {
    let json = FormatterJSON;
    let xml = FormatterXML;

    println!("{}", esporta(&json, "ciao")); // {"dati": "ciao"}
    println!("{}", esporta(&xml, "ciao"));  // <dati>ciao</dati>
}

Trait Objects con Lifetime

trait Logger {
    fn log(&self, messaggio: &str);
}

// Con lifetime esplicita
fn crea_logger<'a>(livello: &'a str) -> Box<dyn Logger + 'a> {
    // ...
    todo!()
}

Quando Usare Trait Objects vs Generics

Criterio Generics Trait Objects
Performance Zero-cost Overhead vtable
Tipi eterogenei No
Dimensione binario Più grande (monomorphization) Più piccolo
Flessibilità runtime No

Conclusione

I trait objects offrono polimorfismo dinamico in Rust tramite dyn Trait. Usali quando hai bisogno di lavorare con tipi eterogenei a runtime (collezioni miste, plugin, strategie). Preferisci i generics quando la performance è critica e i tipi sono noti a compile-time. La regola di object safety garantisce che il compilatore possa costruire la vtable necessaria per il dispatch dinamico.