type (alias de tip și tip asociat) în Rust

Dă un nume nou unui tip existent (alias), ca să scurtezi tipuri lungi; în trait-uri, declară un tip asociat.

Sintaxă
type NumeNou = TipExistent;
type Rezultat<T> = Result<T, EroareaMea>;
trait Trait {
    type Item;            // tip asociat
}

Ce face type (alias de tip și tip asociat)

type creează un alias: un al doilea nume pentru un tip existent. type Catalog = BTreeMap<String, Vec<u8>>; te scutește să scrii tipul lung de fiecare dată. Aliasul nu creează un tip nou: type Km = f64; și f64 sunt complet interschimbabile, deci compilatorul nu te oprește să aduni kilometri cu kilograme. Pentru un tip cu adevărat diferit, folosește un tuple struct (struct Km(f64);).

Un alias poate avea parametri generici. Modelul folosit de biblioteca standard este std::io::Result<T>, definit ca Result<T, std::io::Error>; la fel îți poți defini propriul Rezultat<T>.

În interiorul unui trait, type Nume; declară un tip asociat, pe care fiecare implementare îl alege. Exemplul clasic este Iterator, cu type Item;: fiecare iterator spune ce tip de elemente produce.

Exemple

Exemplul 1: Alias pentru un tip lung și pentru un Result

Catalog și Rezultat<T> scurtează semnăturile. Rezultat<f64> este exact Result<f64, String>.

use std::collections::BTreeMap;

type Catalog = BTreeMap<String, Vec<u8>>;
type Rezultat<T> = Result<T, String>;

fn media(catalog: &Catalog, elev: &str) -> Rezultat<f64> {
    let note = catalog.get(elev).ok_or(format!("{elev} nu există"))?;
    if note.is_empty() {
        return Err(format!("{elev} nu are note"));
    }
    Ok(note.iter().map(|&n| n as f64).sum::<f64>() / note.len() as f64)
}

fn main() {
    let mut catalog = Catalog::new();
    catalog.insert("Ana".to_string(), vec![10, 9, 9]);
    catalog.insert("Radu".to_string(), vec![]);
    for elev in ["Ana", "Radu", "Dan"] {
        match media(&catalog, elev) {
            Ok(m) => println!("{elev}: {m:.2}"),
            Err(e) => println!("eroare: {e}"),
        }
    }
}

Rezultat

Ana: 9.33
eroare: Radu nu are note
eroare: Dan nu există

Exemplul 2: Tip asociat într-un trait: Iterator

type Item = u64; spune că iteratorul Fibonacci produce numere u64. Odată implementat next, toate metodele lui Iterator (take, collect) funcționează.

struct Fibonacci {
    a: u64,
    b: u64,
}

impl Iterator for Fibonacci {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        let urmator = self.a;
        self.a = self.b;
        self.b += urmator;
        Some(urmator)
    }
}

fn main() {
    let primele: Vec<u64> = Fibonacci { a: 0, b: 1 }.take(12).collect();
    println!("{primele:?}");
}

Rezultat

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Greșeli frecvente

  • Crezi că type Lei = f64; te protejează de amestecul cu euro. Aliasul este doar un nume; pentru verificare la compilare folosește struct Lei(f64);.
  • Scrii type pentru a defini un tip nou cu câmpuri. type nu definește structuri; pentru asta există struct și enum.

Comenzi înrudite

  • 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: 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.
  • struct: Definește un tip nou care grupează mai multe valori cu nume (câmpuri), de exemplu un elev cu nume, clasă și medie.
  • operatorul ?: Pus după un Result sau Option: dă valoarea dacă totul e bine, altfel returnează imediat eroarea (sau None) din funcția curentă.