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

Operatori Aritmetici in Rust

Gli operatori aritmetici in Rust sono utilizzati per eseguire operazioni matematiche sui valori numerici. A differenza di molti altri linguaggi, Rust non esegue conversioni implicite tra tipi numerici e offre strumenti avanzati per gestire l’overflow in modo sicuro. In questa guida esploreremo tutti gli operatori aritmetici e le loro particolarita.

Operatori Aritmetici di Base

Rust supporta cinque operatori aritmetici fondamentali:

Operatore Operazione Esempio
+ Addizione 5 + 3 = 8
- Sottrazione 10 - 4 = 6
* Moltiplicazione 3 * 7 = 21
/ Divisione 20 / 4 = 5
% Resto (modulo) 17 % 5 = 2
fn main() {
    let a = 15;
    let b = 4;

    println!("{} + {} = {}", a, b, a + b);   // 19
    println!("{} - {} = {}", a, b, a - b);   // 11
    println!("{} * {} = {}", a, b, a * b);   // 60
    println!("{} / {} = {}", a, b, a / b);   // 3 (divisione intera)
    println!("{} % {} = {}", a, b, a % b);   // 3
}

Nessuna Conversione Implicita

Rust non converte automaticamente tra tipi numerici diversi. Tutti gli operandi devono essere dello stesso tipo:

fn main() {
    let intero: i32 = 10;
    let decimale: f64 = 3.5;

    // let risultato = intero + decimale; // Errore! Tipi diversi

    // Conversione esplicita necessaria
    let risultato = intero as f64 + decimale;
    println!("Risultato: {}", risultato); // 13.5

    let a: i32 = 100;
    let b: i64 = 200;

    // let somma = a + b; // Errore! i32 e i64 sono tipi diversi
    let somma = a as i64 + b;
    println!("Somma: {}", somma); // 300
}

Divisione Intera e in Virgola Mobile

La divisione tra interi tronca la parte decimale (divisione intera), mentre la divisione tra float produce un risultato decimale:

fn main() {
    // Divisione intera: tronca verso zero
    let a: i32 = 7;
    let b: i32 = 2;
    println!("7 / 2 = {} (intero)", a / b); // 3

    let c: i32 = -7;
    let d: i32 = 2;
    println!("-7 / 2 = {} (intero)", c / d); // -3 (tronca verso zero)

    // Divisione float: risultato decimale
    let e: f64 = 7.0;
    let f: f64 = 2.0;
    println!("7.0 / 2.0 = {} (float)", e / f); // 3.5

    // Divisione per zero
    // let errore = 10 / 0; // Panic! Divisione per zero con interi
    let inf = 10.0_f64 / 0.0; // Infinity (IEEE 754)
    println!("10.0 / 0.0 = {}", inf);
}

L’Operatore Modulo (%)

L’operatore resto % restituisce il resto della divisione intera. In Rust, il segno del risultato segue il segno del dividendo:

fn main() {
    println!("17 % 5 = {}", 17 % 5);     // 2
    println!("20 % 4 = {}", 20 % 4);     // 0
    println!("-17 % 5 = {}", -17 % 5);   // -2 (segno del dividendo)
    println!("17 % -5 = {}", 17 % -5);   // 2  (segno del dividendo)

    // Funziona anche con i float
    println!("7.5 % 2.0 = {}", 7.5_f64 % 2.0); // 1.5

    // Uso pratico: verificare se un numero è pari
    let numero = 42;
    if numero % 2 == 0 {
        println!("{} è pari", numero);
    }
}

Operatore Unario di Negazione

L’operatore - unario nega un valore numerico:

fn main() {
    let positivo = 42;
    let negativo = -positivo;
    println!("{}", negativo); // -42

    let x: f64 = 3.14;
    let y = -x;
    println!("{}", y); // -3.14

    // Non funziona con i tipi senza segno
    // let u: u32 = 5;
    // let neg = -u; // Errore! Cannot apply unary operator `-` to type `u32`
}

Checked Operations

Le operazioni checked restituiscono None in caso di overflow, invece di andare in panic:

fn main() {
    let a: i32 = i32::MAX;

    let checked_add = a.checked_add(1);
    println!("MAX + 1 (checked): {:?}", checked_add); // None

    let checked_mul = 100_i32.checked_mul(200);
    println!("100 * 200 (checked): {:?}", checked_mul); // Some(20000)

    let checked_div = 10_i32.checked_div(0);
    println!("10 / 0 (checked): {:?}", checked_div); // None

    // Uso pratico con match
    match a.checked_add(1) {
        Some(risultato) => println!("Risultato: {}", risultato),
        None => println!("Overflow rilevato!"),
    }
}

Wrapping Operations

Le operazioni wrapping eseguono il wrapping aritmetico (complemento a due) in caso di overflow:

fn main() {
    let a: u8 = 255;

    let wrapped = a.wrapping_add(1);
    println!("255 + 1 (wrapping u8): {}", wrapped); // 0

    let wrapped2 = a.wrapping_add(10);
    println!("255 + 10 (wrapping u8): {}", wrapped2); // 9

    let b: i8 = 127;
    let wrapped3 = b.wrapping_add(1);
    println!("127 + 1 (wrapping i8): {}", wrapped3); // -128
}

Saturating Operations

Le operazioni saturating si fermano al valore massimo o minimo del tipo:

fn main() {
    let a: u8 = 250;

    let saturated = a.saturating_add(10);
    println!("250 + 10 (saturating u8): {}", saturated); // 255

    let b: u8 = 5;
    let saturated2 = b.saturating_sub(10);
    println!("5 - 10 (saturating u8): {}", saturated2); // 0

    let c: i8 = 120;
    let saturated3 = c.saturating_add(20);
    println!("120 + 20 (saturating i8): {}", saturated3); // 127

    // Utile per barre di progresso, volumi, ecc.
    let mut volume: u8 = 240;
    volume = volume.saturating_add(30); // Non supera 255
    println!("Volume: {}", volume); // 255
}

Overflowing Operations

Le operazioni overflowing restituiscono una tupla con il risultato e un booleano che indica se c’e stato overflow:

fn main() {
    let a: u8 = 200;

    let (risultato, overflow) = a.overflowing_add(100);
    println!("200 + 100 (u8): {} (overflow: {})", risultato, overflow);
    // Output: 44 (overflow: true)

    let (risultato2, overflow2) = a.overflowing_add(10);
    println!("200 + 10 (u8): {} (overflow: {})", risultato2, overflow2);
    // Output: 210 (overflow: false)
}

Operator Overloading con i Trait

In Rust, gli operatori aritmetici sono implementati tramite i trait del modulo std::ops. È possibile implementarli per i propri tipi:

use std::ops::Add;

#[derive(Debug)]
struct Vettore2D {
    x: f64,
    y: f64,
}

impl Add for Vettore2D {
    type Output = Vettore2D;

    fn add(self, altro: Vettore2D) -> Vettore2D {
        Vettore2D {
            x: self.x + altro.x,
            y: self.y + altro.y,
        }
    }
}

fn main() {
    let v1 = Vettore2D { x: 1.0, y: 2.0 };
    let v2 = Vettore2D { x: 3.0, y: 4.0 };
    let v3 = v1 + v2;

    println!("Risultato: {:?}", v3); // Vettore2D { x: 4.0, y: 6.0 }
}

I trait per gli operatori aritmetici sono: Add (+), Sub (-), Mul (*), Div (/) e Rem (%).

Conclusione

Gli operatori aritmetici in Rust sono familiari nella sintassi ma unici nel loro rigore. L’assenza di conversioni implicite e la gestione esplicita dell’overflow attraverso le varianti checked, wrapping, saturating e overflowing rendono il codice numerico in Rust estremamente sicuro e prevedibile. Questa attenzione ai dettagli, combinata con la possibilita di implementare operator overloading tramite i trait, rende Rust un linguaggio potente e affidabile per il calcolo numerico.