trait în Rust

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.

Sintaxă
trait Nume {
    fn metoda(&self) -> Tip;                 // de implementat
    fn implicita(&self) -> Tip { ... }       // cu implementare implicită
}
impl Nume for TipulMeu { fn metoda(&self) -> Tip { ... } }
fn f(x: &impl Nume) { ... }
fn g<T: Nume>(x: &T) { ... }

Ce face trait

Un trait descrie un comportament: o listă de metode (semnături), eventual cu implementări implicite. Un tip „are” trait-ul după ce scrii impl Trait for Tip { ... } și dai implementările care lipsesc. Același trait poate fi implementat de oricâte tipuri.

Trait-urile sunt folosite ca limite în funcții generice: fn afiseaza(x: &impl Descriere) sau fn afiseaza<T: Descriere>(x: &T) acceptă orice tip care implementează Descriere. Pentru o colecție de tipuri diferite cu același trait se folosesc trait objects (Box<dyn Trait>).

Biblioteca standard are multe trait-uri pe care le implementezi des: Display (afișare cu {}), Debug, Clone, PartialEq, Default, From, Iterator. Multe se pot genera automat cu #[derive(...)]. Ca să folosești metodele unui trait, trait-ul trebuie să fie în domeniu (importat cu use), dacă nu este în preludiu.

Exemple

Exemplul 1: Un trait cu o metodă implicită

Forma cere arie și nume; descriere are implementare implicită, folosită de ambele tipuri. Funcția raport acceptă orice tip care implementează Forma.

trait Forma {
    fn arie(&self) -> f64;
    fn nume(&self) -> String;

    fn descriere(&self) -> String {
        format!("{} cu aria {:.2}", self.nume(), self.arie())
    }
}

struct Cerc {
    raza: f64,
}

struct Dreptunghi {
    latime: f64,
    inaltime: f64,
}

impl Forma for Cerc {
    fn arie(&self) -> f64 {
        std::f64::consts::PI * self.raza * self.raza
    }
    fn nume(&self) -> String {
        "cerc".to_string()
    }
}

impl Forma for Dreptunghi {
    fn arie(&self) -> f64 {
        self.latime * self.inaltime
    }
    fn nume(&self) -> String {
        format!("dreptunghi {}x{}", self.latime, self.inaltime)
    }
}

fn raport(f: &impl Forma) {
    println!("- {}", f.descriere());
}

fn main() {
    raport(&Cerc { raza: 2.0 });
    raport(&Dreptunghi { latime: 3.0, inaltime: 4.5 });
}

Rezultat

- cerc cu aria 12.57
- dreptunghi 3x4.5 cu aria 13.50

Exemplul 2: Implementarea unui trait standard: From

După impl From<u32> for Bani, Rust oferă automat și into() în sens invers. #[derive(Debug, PartialEq)] generează alte două trait-uri fără cod scris de mână.

#[derive(Debug, PartialEq)]
struct Bani {
    lei: u32,
    bani: u32,
}

impl From<u32> for Bani {
    fn from(total_bani: u32) -> Self {
        Bani { lei: total_bani / 100, bani: total_bani % 100 }
    }
}

fn main() {
    let pret = Bani::from(1_299);
    let rest: Bani = 450.into();
    println!("{pret:?}");
    println!("{rest:?}");
    println!("{}", rest == Bani { lei: 4, bani: 50 });
}

Rezultat

Bani { lei: 12, bani: 99 }
Bani { lei: 4, bani: 50 }
true

Exemplul 3: Trait pentru un tip din biblioteca standard

Poți implementa propriul trait și pentru tipuri existente, de exemplu str. Regula (orphan rule): trait-ul sau tipul trebuie să fie definit în crate-ul tău.

trait Initiale {
    fn initiale(&self) -> String;
}

impl Initiale for str {
    fn initiale(&self) -> String {
        self.split_whitespace()
            .filter_map(|cuvant| cuvant.chars().next())
            .map(|c| format!("{c}."))
            .collect()
    }
}

fn main() {
    println!("{}", "Mihai Eminescu".initiale());
    println!("{}", "Ion Luca Caragiale".initiale());
}

Rezultat

M.E.
I.L.C.

Greșeli frecvente

  • Apelezi metoda unui trait care nu este importat: no method named `...` found, iar compilatorul îți sugerează use-ul necesar.
  • Încerci să implementezi un trait străin pentru un tip străin (impl Display for Vec<i32>): eroarea E0117 (orphan rule). Împachetează tipul într-o structură proprie (struct Lista(Vec<i32>);).
  • Uiți una dintre metodele obligatorii: E0046 not all trait items implemented, cu lista metodelor lipsă.

Comenzi înrudite

  • impl, self și Self: impl adaugă metode și funcții asociate unui tip; self este valoarea pe care se apelează metoda, iar Self este tipul însuși.
  • 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.
  • trait objects (dyn Trait): dyn Trait înseamnă „o valoare de orice tip care implementează trait-ul”, aleasă la rulare. Permite colecții cu tipuri diferite.
  • 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.
  • atribute (#[derive], #[cfg], #[allow]): Informații pentru compilator scrise cu #[…] deasupra unui element: generează cod (derive), aleg codul compilat (cfg) sau schimbă avertismentele.