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

Tipi di Dati in Rust

Rust è un linguaggio staticamente tipizzato: il tipo di ogni variabile deve essere noto a tempo di compilazione. Grazie all’inferenza dei tipi, il compilatore è spesso in grado di determinare automaticamente il tipo, ma in alcuni casi è necessario specificarlo esplicitamente. In questa guida esploreremo i principali tipi di dati disponibili in Rust.

Tipi Scalari

I tipi scalari rappresentano un singolo valore. Rust ha quattro categorie di tipi scalari.

Interi

I tipi interi rappresentano numeri senza parte decimale. Rust offre interi con e senza segno di diverse dimensioni:

fn main() {
    let intero_con_segno: i32 = -42;    // Da -2^31 a 2^31 - 1
    let intero_senza_segno: u32 = 42;   // Da 0 a 2^32 - 1
    let byte: u8 = 255;                  // Da 0 a 255
    let grande: i64 = 1_000_000_000;     // Intero a 64 bit

    println!("{}, {}, {}, {}", intero_con_segno, intero_senza_segno, byte, grande);
}

I tipi disponibili sono: i8, i16, i32, i64, i128, isize (con segno) e u8, u16, u32, u64, u128, usize (senza segno). Il tipo predefinito per i letterali interi è i32.

Numeri in Virgola Mobile

Rust ha due tipi per i numeri decimali: f32 e f64. Il tipo predefinito è f64:

fn main() {
    let x = 2.0;          // f64 (predefinito)
    let y: f32 = 3.14;    // f32

    println!("x = {}, y = {}", x, y);
}

Booleani

Il tipo bool può avere solo due valori: true e false. Occupa 1 byte in memoria:

fn main() {
    let vero: bool = true;
    let falso = false;     // Tipo inferito come bool

    println!("vero: {}, falso: {}", vero, falso);
}

Caratteri

Il tipo char in Rust rappresenta un valore scalare Unicode e occupa 4 byte. Può rappresentare molto più dei soli caratteri ASCII:

fn main() {
    let lettera = 'a';
    let emoji = '🦀';
    let ideogramma = 'æ¼¢';
    let accento: char = 'è';

    println!("{} {} {} {}", lettera, emoji, ideogramma, accento);
}

Tipi Composti

I tipi composti raggruppano più valori in un singolo tipo. Rust ha due tipi composti primitivi: tuple e array.

Tuple

Una tupla raggruppa valori di tipi diversi in un unico tipo composto. Ha una lunghezza fissa:

fn main() {
    let persona: (&str, i32, bool) = ("Alice", 30, true);

    // Accesso tramite destructuring
    let (nome, eta, attiva) = persona;
    println!("Nome: {}, Età: {}, Attiva: {}", nome, eta, attiva);

    // Accesso tramite indice (con punto e numero)
    println!("Nome: {}", persona.0);
    println!("Età: {}", persona.1);
    println!("Attiva: {}", persona.2);
}

La tupla vuota () è chiamata unit type ed è il valore restituito dalle funzioni che non restituiscono nulla esplicitamente:

fn non_restituisce_nulla() {
    println!("Questa funzione restituisce ()");
}

fn main() {
    let risultato: () = non_restituisce_nulla();
    println!("Risultato: {:?}", risultato); // Stampa: ()
}

Array

Un array contiene una collezione di valori dello stesso tipo con lunghezza fissa, nota a tempo di compilazione:

fn main() {
    let numeri: [i32; 5] = [1, 2, 3, 4, 5];
    let primo = numeri[0];
    let ultimo = numeri[4];

    println!("Primo: {}, Ultimo: {}", primo, ultimo);
    println!("Lunghezza: {}", numeri.len());

    // Array inizializzato con lo stesso valore
    let zeri = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    println!("Zeri: {:?}", zeri);
}

L’accesso a un indice fuori dai limiti causa un panic a runtime, non un comportamento indefinito come in C:

fn main() {
    let arr = [1, 2, 3];
    // arr[5]; // Panic! index out of bounds: the len is 3 but the index is 5
}

Inferenza dei Tipi

Rust ha un potente sistema di inferenza dei tipi che nella maggior parte dei casi deduce il tipo corretto:

fn main() {
    let x = 5;              // i32 (inferito)
    let y = 3.14;           // f64 (inferito)
    let attivo = true;      // bool (inferito)
    let nome = "Rust";      // &str (inferito)

    let numeri = vec![1, 2, 3]; // Vec<i32> (inferito)

    println!("x: {}, y: {}, attivo: {}, nome: {}", x, y, attivo, nome);
    println!("numeri: {:?}", numeri);
}

Talvolta l’inferenza necessita di aiuto, specialmente con metodi generici:

fn main() {
    // Il compilatore non sa quale tipo numerico parsare
    // let numero = "42".parse().unwrap(); // Errore!

    let numero: i32 = "42".parse().unwrap();       // OK: tipo annotato
    let numero2 = "42".parse::<f64>().unwrap();    // OK: turbofish syntax

    println!("i32: {}, f64: {}", numero, numero2);
}

Annotazione dei Tipi

Quando l’inferenza non è sufficiente o si vuole essere espliciti, si annotano i tipi:

fn main() {
    let temperatura: f64 = 36.6;
    let contatore: u64 = 0;
    let messaggio: &str = "Ciao mondo";
    let coordinate: (f64, f64) = (45.46, 9.19);
    let punteggi: [u32; 3] = [100, 95, 88];

    println!("Temperatura: {}°C", temperatura);
    println!("Coordinate: {:?}", coordinate);
    println!("Punteggi: {:?}", punteggi);
}

Alias di Tipo con type

La parola chiave type crea un alias per un tipo esistente. Non crea un nuovo tipo, ma un nome alternativo:

type Chilometri = f64;
type Risultato = Result<String, std::io::Error>;
type Punto2D = (f64, f64);

fn distanza(p1: Punto2D, p2: Punto2D) -> Chilometri {
    let dx = p2.0 - p1.0;
    let dy = p2.1 - p1.1;
    (dx * dx + dy * dy).sqrt()
}

fn main() {
    let roma: Punto2D = (41.9, 12.5);
    let milano: Punto2D = (45.5, 9.2);

    let dist: Chilometri = distanza(roma, milano);
    println!("Distanza approssimativa: {:.2}", dist);
}

Gli alias sono utili per rendere il codice più leggibile, specialmente con tipi complessi:

type CallbackFn = Box<dyn Fn(i32) -> i32>;

fn applica(callback: &CallbackFn, valore: i32) -> i32 {
    callback(valore)
}

fn main() {
    let raddoppia: CallbackFn = Box::new(|x| x * 2);
    let risultato = applica(&raddoppia, 21);
    println!("Risultato: {}", risultato); // Stampa: 42
}

Conclusione

Il sistema di tipi di Rust è uno dei suoi punti di forza principali. La combinazione di tipi scalari e composti copre le esigenze fondamentali della programmazione, mentre l’inferenza dei tipi mantiene il codice conciso senza sacrificare la sicurezza. Gli alias di tipo permettono di rendere il codice più leggibile e manutenibile. Comprendere i tipi di dati di Rust è essenziale per sfruttare appieno il sistema di ownership e le garanzie di sicurezza del linguaggio.