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

Metodi e Blocchi impl in Rust

In Rust, i metodi sono funzioni associate a un tipo specifico, definite all’interno di blocchi impl. A differenza di altri linguaggi orientati agli oggetti, Rust separa la definizione dei dati (struct) dalla definizione del comportamento (impl), mantenendo il codice modulare e chiaro.

Definire Metodi con impl

Un blocco impl collega metodi a una struct (o enum, o trait). Il primo parametro di un metodo e sempre una variante di self, che rappresenta l’istanza su cui il metodo viene chiamato.

#[derive(Debug)]
struct Rettangolo {
    larghezza: f64,
    altezza: f64,
}

impl Rettangolo {
    fn area(&self) -> f64 {
        self.larghezza * self.altezza
    }

    fn perimetro(&self) -> f64 {
        2.0 * (self.larghezza + self.altezza)
    }
}

fn main() {
    let rett = Rettangolo {
        larghezza: 10.0,
        altezza: 5.0,
    };

    println!("Area: {}", rett.area());
    println!("Perimetro: {}", rett.perimetro());
}

self, &self e &mut self

La scelta tra le diverse forme di self determina come il metodo interagisce con l’istanza:

  • &self - prende un riferimento immutabile (il piu comune)
  • &mut self - prende un riferimento mutabile, puo modificare l’istanza
  • self - prende l’ownership, consuma l’istanza
struct Contatore {
    valore: i32,
}

impl Contatore {
    // &self: legge senza modificare
    fn valore(&self) -> i32 {
        self.valore
    }

    // &mut self: modifica l'istanza
    fn incrementa(&mut self) {
        self.valore += 1;
    }

    // self: consuma l'istanza e la trasforma
    fn azzera(self) -> Contatore {
        Contatore { valore: 0 }
    }
}

fn main() {
    let mut c = Contatore { valore: 0 };

    c.incrementa();
    c.incrementa();
    c.incrementa();
    println!("Valore: {}", c.valore()); // 3

    let c = c.azzera(); // c originale consumato, nuova istanza creata
    println!("Dopo azzeramento: {}", c.valore()); // 0
}

Funzioni Associate (Costruttori)

Le funzioni associate sono funzioni definite nel blocco impl che non prendono self come primo parametro. Si chiamano usando la sintassi Tipo::funzione(). Il pattern piu comune e il costruttore new.

struct Cerchio {
    raggio: f64,
    centro: (f64, f64),
}

impl Cerchio {
    // Costruttore classico
    fn new(raggio: f64, x: f64, y: f64) -> Self {
        Self {
            raggio,
            centro: (x, y),
        }
    }

    // Costruttore alternativo
    fn unitario() -> Self {
        Self {
            raggio: 1.0,
            centro: (0.0, 0.0),
        }
    }

    fn area(&self) -> f64 {
        std::f64::consts::PI * self.raggio * self.raggio
    }

    fn circonferenza(&self) -> f64 {
        2.0 * std::f64::consts::PI * self.raggio
    }
}

fn main() {
    let c1 = Cerchio::new(5.0, 0.0, 0.0);
    let c2 = Cerchio::unitario();

    println!("Cerchio 1 - Area: {:.2}", c1.area());
    println!("Cerchio 2 - Circonferenza: {:.2}", c2.circonferenza());
}

Self (con la S maiuscola) e un alias per il tipo su cui e implementato il blocco impl, rendendo il codice piu manutenibile.

Blocchi impl Multipli

E possibile avere piu blocchi impl per lo stesso tipo. Questo e utile per organizzare il codice o per implementare metodi condizionati da trait bounds.

struct Punto {
    x: f64,
    y: f64,
}

// Primo blocco: costruttori
impl Punto {
    fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    fn origine() -> Self {
        Self { x: 0.0, y: 0.0 }
    }
}

// Secondo blocco: operazioni geometriche
impl Punto {
    fn distanza_da(&self, altro: &Punto) -> f64 {
        ((self.x - altro.x).powi(2) + (self.y - altro.y).powi(2)).sqrt()
    }

    fn punto_medio(&self, altro: &Punto) -> Punto {
        Punto {
            x: (self.x + altro.x) / 2.0,
            y: (self.y + altro.y) / 2.0,
        }
    }
}

fn main() {
    let p1 = Punto::new(3.0, 4.0);
    let p2 = Punto::origine();

    println!("Distanza dall'origine: {:.2}", p1.distanza_da(&p2));

    let medio = p1.punto_medio(&p2);
    println!("Punto medio: ({:.1}, {:.1})", medio.x, medio.y);
}

Method Chaining (Restituire Self)

Il method chaining e un pattern in cui ogni metodo restituisce Self (o &mut Self), permettendo di concatenare piu chiamate in una singola espressione.

#[derive(Debug)]
struct QueryBuilder {
    tabella: String,
    condizioni: Vec<String>,
    limite: Option<usize>,
    ordinamento: Option<String>,
}

impl QueryBuilder {
    fn new(tabella: &str) -> Self {
        Self {
            tabella: tabella.to_string(),
            condizioni: Vec::new(),
            limite: None,
            ordinamento: None,
        }
    }

    fn dove(mut self, condizione: &str) -> Self {
        self.condizioni.push(condizione.to_string());
        self
    }

    fn limite(mut self, n: usize) -> Self {
        self.limite = Some(n);
        self
    }

    fn ordina_per(mut self, campo: &str) -> Self {
        self.ordinamento = Some(campo.to_string());
        self
    }

    fn costruisci(&self) -> String {
        let mut query = format!("SELECT * FROM {}", self.tabella);
        if !self.condizioni.is_empty() {
            query.push_str(&format!(" WHERE {}", self.condizioni.join(" AND ")));
        }
        if let Some(ref campo) = self.ordinamento {
            query.push_str(&format!(" ORDER BY {}", campo));
        }
        if let Some(limite) = self.limite {
            query.push_str(&format!(" LIMIT {}", limite));
        }
        query
    }
}

fn main() {
    let query = QueryBuilder::new("utenti")
        .dove("eta > 18")
        .dove("attivo = true")
        .ordina_per("nome")
        .limite(10)
        .costruisci();

    println!("{}", query);
    // SELECT * FROM utenti WHERE eta > 18 AND attivo = true ORDER BY nome LIMIT 10
}

Conclusione

I blocchi impl sono il cuore della programmazione orientata ai tipi in Rust. Permettono di associare comportamenti alle struct in modo chiaro e organizzato. La distinzione tra &self, &mut self e self rende esplicito il livello di accesso ai dati, le funzioni associate offrono un modo naturale per definire costruttori, e il method chaining rende il codice espressivo e fluido. Combinati con i trait, i blocchi impl formano la base del polimorfismo in Rust.