00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Generics in Rust

I generics in Rust permettono di scrivere codice che funziona con diversi tipi senza duplicazione. Grazie alla monomorfizzazione, i generics hanno zero costi a runtime.

Funzioni Generiche

fn massimo<T: PartialOrd>(a: T, b: T) -> T {
    if a >= b { a } else { b }
}

fn main() {
    println!("{}", massimo(10, 20));      // 20
    println!("{}", massimo(3.14, 2.71));  // 3.14
    println!("{}", massimo("abc", "xyz")); // xyz
}

Struct Generiche

#[derive(Debug)]
struct Punto<T> {
    x: T,
    y: T,
}

// Struct con tipi diversi
#[derive(Debug)]
struct Coppia<A, B> {
    primo: A,
    secondo: B,
}

fn main() {
    let intero = Punto { x: 5, y: 10 };
    let decimale = Punto { x: 1.5, y: 2.5 };
    let misto = Coppia { primo: "nome", secondo: 42 };
}

Impl con Generics

impl<T> Punto<T> {
    fn new(x: T, y: T) -> Self {
        Punto { x, y }
    }

    fn x(&self) -> &T {
        &self.x
    }
}

// Implementazione solo per un tipo specifico
impl Punto<f64> {
    fn distanza_da_origine(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

Enum Generici

Option<T> e Result<T, E> sono gli esempi più comuni:

enum Opzione<T> {
    Qualcosa(T),
    Niente,
}

enum Risultato<T, E> {
    Ok(T),
    Err(E),
}

Trait Bounds

Limitano i tipi accettati da un generico:

use std::fmt::Display;

// Sintassi con :
fn stampa<T: Display>(valore: T) {
    println!("{}", valore);
}

// Sintassi con where (più leggibile per bounds complessi)
fn elabora<T, U>(t: T, u: U) -> String
where
    T: Display + Clone,
    U: Display + Debug,
{
    format!("{} - {:?}", t, u)
}

// Sintassi impl Trait (per parametri)
fn stampa_display(valore: impl Display) {
    println!("{}", valore);
}

Bounds Multipli

use std::fmt::{Display, Debug};

fn confronta_e_stampa<T: PartialOrd + Display>(a: T, b: T) {
    if a > b {
        println!("{} è maggiore di {}", a, b);
    } else {
        println!("{} è minore o uguale a {}", a, b);
    }
}

Const Generics

Permettono di parametrizzare sui valori costanti:

fn stampa_array<T: Debug, const N: usize>(arr: [T; N]) {
    println!("{:?}", arr);
}

struct Buffer<const N: usize> {
    dati: [u8; N],
}

fn main() {
    stampa_array([1, 2, 3]);        // N = 3
    stampa_array([1, 2, 3, 4, 5]);  // N = 5

    let buf = Buffer::<1024> { dati: [0; 1024] };
}

Monomorfizzazione

Il compilatore genera codice specifico per ogni tipo concreto usato:

fn doppio<T: std::ops::Mul<Output = T> + Copy>(x: T) -> T {
    x * x
}

// Il compilatore genera:
// fn doppio_i32(x: i32) -> i32 { x * x }
// fn doppio_f64(x: f64) -> f64 { x * x }

fn main() {
    doppio(5_i32);  // Usa la versione i32
    doppio(3.14);   // Usa la versione f64
}

Questo garantisce zero overhead rispetto al codice scritto manualmente per ogni tipo.

Conclusione

I generics sono uno dei pilastri di Rust, permettendo di scrivere codice riutilizzabile e type-safe senza sacrificare le performance. I trait bounds garantiscono che i tipi generici soddisfino i requisiti necessari, e la monomorfizzazione elimina qualsiasi costo a runtime. Usa where per bounds complessi e i const generics per parametrizzare sulla dimensione.