Ce face generics și where
Generics înseamnă cod parametrizat după tip. fn maxim<T: PartialOrd>(lista: &[T]) -> &T funcționează pentru numere, texte sau orice tip care se poate compara. T este un parametru de tip, iar : PartialOrd este o limită (trait bound): fără ea, compilatorul nu te lasă să folosești > pe valori de tip T.
Mai multe limite se scriu cu + (T: Display + Clone). Când lista devine lungă, ele se mută într-o clauză where după semnătură, ca funcția să rămână lizibilă. Structurile și enum-urile pot fi și ele generice: Vec<T>, Option<T>, HashMap<K, V> sunt exemple din biblioteca standard.
Generics nu costă nimic la rulare: compilatorul generează o versiune separată pentru fiecare tip folosit (monomorfizare), la fel de rapidă ca una scrisă de mână. Parametrii const N: usize fac tipuri generice după un număr, de exemplu array-uri de orice lungime.
Exemple
Exemplul 1: Funcție generică cu limită
Aceeași funcție maxim găsește cea mai mare notă, cea mai mare temperatură (f64) și ultimul oraș în ordine alfabetică.
fn maxim<T: PartialOrd>(lista: &[T]) -> &T {
let mut cel_mai_mare = &lista[0];
for element in lista {
if element > cel_mai_mare {
cel_mai_mare = element;
}
}
cel_mai_mare
}
fn main() {
println!("{}", maxim(&[7, 10, 9, 8]));
println!("{}", maxim(&[21.5, 24.1, 19.8]));
println!("{}", maxim(&["Iași", "Arad", "Timișoara", "Cluj"]));
}
Rezultat
10
24.1
Timișoara
Exemplul 2: Structură generică și clauză where
Pereche<T> ține două valori de același tip. Metoda afiseaza_mai_mare există doar pentru tipurile care se pot compara și afișa, condiție scrisă în where.
use std::fmt::Display;
struct Pereche<T> {
a: T,
b: T,
}
impl<T> Pereche<T> {
fn new(a: T, b: T) -> Self {
Pereche { a, b }
}
}
impl<T> Pereche<T>
where
T: PartialOrd + Display,
{
fn afiseaza_mai_mare(&self) {
if self.a >= self.b {
println!("mai mare: {}", self.a);
} else {
println!("mai mare: {}", self.b);
}
}
}
fn main() {
Pereche::new(15, 42).afiseaza_mai_mare();
Pereche::new("Ana", "Bogdan").afiseaza_mai_mare();
}
Rezultat
mai mare: 42
mai mare: Bogdan
Exemplul 3: Parametru generic constant
N este lungimea array-ului, cunoscută la compilare. Aceeași funcție primește array-uri de lungimi diferite, fără să le transforme în slice-uri.
fn medie<const N: usize>(note: [f64; N]) -> f64 {
note.iter().sum::<f64>() / N as f64
}
fn main() {
println!("{:.2}", medie([9.0, 8.5, 10.0]));
println!("{:.2}", medie([6.0, 7.0, 8.0, 9.0, 10.0]));
}
Rezultat
9.17
8.00
Greșeli frecvente
- Folosești un operator pe
Tfără limita potrivită:binary operation `>` cannot be applied to type `T`. Compilatorul îți spune ce limită să adaugi (T: PartialOrd). - Faci totul generic de la început. Generics complică semnăturile; începe cu tipuri concrete și generalizează când ai cu adevărat mai multe tipuri.
- Pui limite pe definiția structurii (
struct Cutie<T: Display>). De obicei este mai bine să pui limitele doar pe blocurileimplsau funcțiile care au nevoie de ele.
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.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.lifetimes (durate de viață, 'a): Adnotări ca ‘a care spun compilatorului cât timp trebuie să rămână valide referințele, ca să nu existe niciodată o referință spre memorie eliberată.type (alias de tip și tip asociat): Dă un nume nou unui tip existent (alias), ca să scurtezi tipuri lungi; în trait-uri, declară un tip asociat.