Ce face trait objects (dyn Trait)
Un vector conține elemente de un singur tip. Dacă vrei un vector cu un cerc, un pătrat și un triunghi, toate cu trait-ul Forma, folosești Vec<Box<dyn Forma>>. dyn Forma este un trait object: „ceva care implementează Forma”, cu tipul exact aflat abia la rulare.
Pentru că tipul exact nu se cunoaște la compilare, nici mărimea lui nu se cunoaște, deci un trait object stă mereu în spatele unui pointer: Box<dyn T>, &dyn T, Rc<dyn T>. Apelul de metodă trece printr-un tabel de funcții (vtable) – dispatch dinamic, puțin mai lent decât generics, dar fără cod duplicat.
Nu orice trait poate fi folosit cu dyn: trait-ul trebuie să fie dyn compatible (numit înainte „object safe”). De exemplu, metodele lui nu pot fi generice și nu pot returna Self. Alege generics (impl Trait, T: Trait) când tipul este același în tot apelul și dyn când ai nevoie să amesteci tipuri.
Exemple
Exemplul 1: O colecție de tipuri diferite
Vectorul conține o reducere procentuală și una fixă, tipuri diferite cu același trait. Fiecare apel aplica ajunge la implementarea tipului real.
trait Reducere {
fn aplica(&self, pret: f64) -> f64;
fn descriere(&self) -> String;
}
struct Procent(f64);
struct Fixa(f64);
impl Reducere for Procent {
fn aplica(&self, pret: f64) -> f64 {
pret * (1.0 - self.0 / 100.0)
}
fn descriere(&self) -> String {
format!("-{}%", self.0)
}
}
impl Reducere for Fixa {
fn aplica(&self, pret: f64) -> f64 {
(pret - self.0).max(0.0)
}
fn descriere(&self) -> String {
format!("-{} lei", self.0)
}
}
fn main() {
let reduceri: Vec<Box<dyn Reducere>> = vec![Box::new(Procent(10.0)), Box::new(Fixa(25.0))];
let mut pret = 300.0;
for r in &reduceri {
pret = r.aplica(pret);
println!("după {:>8}: {pret:.2} lei", r.descriere());
}
}
Rezultat
după -10%: 270.00 lei
după -25 lei: 245.00 lei
Exemplul 2: &dyn Trait ca parametru și alegerea la rulare
Tipul de notificare este ales la rulare, după un text. Funcția trimite primește &dyn Notificare și nu știe ce tip concret primește.
trait Notificare {
fn trimite(&self, mesaj: &str) -> String;
}
struct Sms;
struct Email {
adresa: String,
}
impl Notificare for Sms {
fn trimite(&self, mesaj: &str) -> String {
format!("SMS: {mesaj}")
}
}
impl Notificare for Email {
fn trimite(&self, mesaj: &str) -> String {
format!("E-mail către {}: {mesaj}", self.adresa)
}
}
fn alege(canal: &str) -> Box<dyn Notificare> {
match canal {
"sms" => Box::new(Sms),
_ => Box::new(Email { adresa: "ana@exemplu.ro".to_string() }),
}
}
fn anunta(n: &dyn Notificare) {
println!("{}", n.trimite("Comanda ta a fost livrată"));
}
fn main() {
for canal in ["sms", "email"] {
let n = alege(canal);
anunta(n.as_ref());
}
}
Rezultat
SMS: Comanda ta a fost livrată
E-mail către ana@exemplu.ro: Comanda ta a fost livrată
Greșeli frecvente
- Scrii
Vec<dyn Forma>fărăBox:the size for values of type `dyn Forma` cannot be known. Pune trait object-ul în spatele unui pointer. - Omiți
dyn(Box<Forma>), cum se scria în codul vechi. În ediția 2021 și 2024 este eroare; scrieBox<dyn Forma>. - Încerci să folosești cu
dynun trait cu metode generice sau care returneazăSelf(de exempluClone): eroareathe trait cannot be made into an object/is not dyn compatible.
Comenzi înrudite
trait: Definește un set de metode pe care un tip promite să le aibă. Seamănă cu interfețele din alte limbaje și stă la baza generics.impl Trait: Scrie „un tip care implementează Trait” fără să-l numești: ca parametru este o scurtătură pentru generics, ca rezultat ascunde tipul exact.generics și where: Scrii o funcție sau un tip o singură dată, pentru orice tip T; limitele (T: Trait) spun ce trebuie să știe să facă T.struct: Definește un tip nou care grupează mai multe valori cu nume (câmpuri), de exemplu un elev cu nume, clasă și medie.