impl, self și Self în Rust

impl adaugă metode și funcții asociate unui tip; self este valoarea pe care se apelează metoda, iar Self este tipul însuși.

Sintaxă
impl Tip {
    fn new(...) -> Self { ... }        // funcție asociată (fără self)
    fn citeste(&self) -> ... { ... }   // metodă care citește
    fn modifica(&mut self) { ... }     // metodă care modifică
    fn consuma(self) -> ... { ... }    // metodă care ia valoarea
}
impl Trait for Tip { ... }

Ce face impl, self și Self

Un bloc impl Tip { ... } conține funcțiile care aparțin tipului. Cele care au primul parametru self sunt metode și se apelează cu punct: cont.depune(100). Cele fără self sunt funcții asociate și se apelează cu ::: Cont::new("Ana"). Prin convenție, constructorul se numește new.

Forma lui self spune ce face metoda cu valoarea: &self doar citește, &mut self modifică (iar variabila trebuie să fie mut), iar self ia valoarea (după apel, ea nu mai poate fi folosită – util pentru transformări ca into_...). La apel, Rust adaugă singur & sau &mut.

Self (cu majusculă) înseamnă „tipul pentru care scriu acest impl”, ca să nu repeți numele: fn new() -> Self, Self { ... }. impl Trait for Tip implementează un trait (de exemplu Display) pentru tip. Poți avea mai multe blocuri impl pentru același tip.

Exemple

Exemplul 1: Constructor, metode care citesc și metode care modifică

new returnează Self, adică ContBancar. depune și retrage primesc &mut self, deci cont trebuie declarat cu let mut. retrage returnează Result când suma nu este disponibilă.

struct ContBancar {
    titular: String,
    sold: u64,
}

impl ContBancar {
    fn new(titular: &str) -> Self {
        Self { titular: titular.to_string(), sold: 0 }
    }

    fn depune(&mut self, suma: u64) {
        self.sold += suma;
    }

    fn retrage(&mut self, suma: u64) -> Result<(), String> {
        if suma > self.sold {
            return Err(format!("fonduri insuficiente: ai {} lei", self.sold));
        }
        self.sold -= suma;
        Ok(())
    }

    fn raport(&self) -> String {
        format!("{}: {} lei", self.titular, self.sold)
    }
}

fn main() {
    let mut cont = ContBancar::new("Ana Popescu");
    cont.depune(500);
    println!("{}", cont.raport());
    if let Err(e) = cont.retrage(800) {
        println!("Eroare: {e}");
    }
    cont.retrage(120).unwrap();
    println!("{}", cont.raport());
}

Rezultat

Ana Popescu: 500 lei
Eroare: fonduri insuficiente: ai 500 lei
Ana Popescu: 380 lei

Exemplul 2: Metodă care consumă valoarea (self)

trimite primește self, deci după apel email nu mai poate fi folosit – ceea ce are sens, un e-mail se trimite o singură dată. cu_subiect folosește același stil pentru a construi pas cu pas valoarea.

struct Email {
    catre: String,
    subiect: String,
}

impl Email {
    fn catre(adresa: &str) -> Self {
        Self { catre: adresa.to_string(), subiect: String::new() }
    }

    fn cu_subiect(mut self, subiect: &str) -> Self {
        self.subiect = subiect.to_string();
        self
    }

    fn trimite(self) -> String {
        format!("Trimis către {}: „{}”", self.catre, self.subiect)
    }
}

fn main() {
    let email = Email::catre("ana@exemplu.ro").cu_subiect("Factura din octombrie");
    println!("{}", email.trimite());
}

Rezultat

Trimis către ana@exemplu.ro: „Factura din octombrie”

Exemplul 3: impl Trait for Tip: implementarea lui Display

După ce Temperatura implementează Display, poate fi afișată cu {} și primește automat metoda to_string().

use std::fmt;

struct Temperatura {
    oras: String,
    grade: f64,
}

impl fmt::Display for Temperatura {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}: {:.1}°C", self.oras, self.grade)
    }
}

fn main() {
    let t = Temperatura { oras: "Suceava".to_string(), grade: -3.4 };
    println!("{t}");
    let text = t.to_string();
    println!("{} caractere", text.chars().count());
}

Rezultat

Suceava: -3.4°C
15 caractere

Greșeli frecvente

  • Apelezi o funcție asociată cu punct (cont.new()) sau o metodă cu :: fără argumentul self. Funcțiile fără self se apelează Tip::functie().
  • Apelezi o metodă cu &mut self pe o variabilă declarată fără mut: eroarea cannot borrow `cont` as mutable.
  • Scrii self.camp fără self. în metodă. Spre deosebire de Java sau C++, câmpurile se accesează întotdeauna prin self.
  • Pui metode care doar citesc pe self în loc de &self: după primul apel, valoarea nu mai poate fi folosită.

Comenzi înrudite

  • struct: Definește un tip nou care grupează mai multe valori cu nume (câmpuri), de exemplu un elev cu nume, clasă și medie.
  • enum: Definește un tip care poate fi exact una dintre mai multe variante; fiecare variantă poate avea propriile date.
  • 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.
  • 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.
  • borrowing și referințe (& și &mut): O referință (&) împrumută o valoare fără să o ia de la proprietar. Regula: oricâte referințe de citire SAU una singură de modificare.