Nel linguaggio Rust, come in molti linguaggi di programmazione moderni, è implementata un sistema di inferenza dei tipi (type inference) che aiuta i programmatori a risparmiare tempo e a ridurre la quantità di codice duplicato. È stata introdotta in Rust praticamente fin dall'inizio, per semplificare la tipizzazione statica senza la necessità di specificare esplicitamente il tipo di una variabile in ogni caso.
Sebbene l'inferenza dei tipi acceleri il lavoro e renda il codice più conciso, un utilizzo troppo frequente o incontrollato di essa può portare a errori non ovvi, a una riduzione della leggibilità e a problemi di performance inaspettati. Non in tutte le situazioni il compilatore può dedurre correttamente o in modo univoco il tipo. Alcune costruttive di Rust richiedono annotazioni esplicite, altrimenti il codice non si compilerà.
Rust supporta l'inferenza dei tipi locale (scope locale) e contestuale. La maggior parte delle volte, l'inferenza dei tipi funziona per variabili, valori restituiti dalle funzioni e anche per espressioni let all'interno delle funzioni. In tutti gli altri casi (ad esempio, durante la dichiarazione di strutture, le firme delle funzioni, le funzioni generic) è necessario specificare i tipi.
Esempio di codice:
let x = 10; // x: i32 (di default) let y = vec!["hello", "world"]; // y: Vec<&str> fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T { a + b } let sum = add(2u16, 3u16); // sum: u16
Caratteristiche chiave:
Può il compilatore Rust dedurre il tipo di una funzione se non viene specificato esplicitamente il valore restituito?
No, nella firma della funzione il tipo di valore restituito deve sempre essere specificato esplicitamente, altrimenti si verifica un errore di compilazione.
// Genererà errore: fn func() { 42 } // Deve essere così: fn func() -> i32 { 42 }
Si può contare completamente sull'inferenza dei tipi quando si lavora con collezioni o riferimenti?
Sovente è necessaria un'annotazione esplicita, soprattutto con riferimenti mutable/immutable e collezioni generiche complesse, per evitare ambiguità o ottenere il tipo desiderato.
let data = Vec::new(); // data: Vec<()> — non sempre è il tipo atteso let numbers: Vec<i32> = Vec::new(); // specificato esplicitamente
Come funziona l'inferenza dei tipi quando si utilizzano chiusure e parametri di funzioni?
Il compilatore può dedurre i tipi dei parametri delle closure in base al contesto, ma non sempre — a volte sarà necessaria un'annotazione completa.
let plus_one = |x| x + 1; // errore: impossibile dedurre il tipo di x let plus_one = |x: i32| x + 1; // si compila
Uno sviluppatore ha scritto una funzione API con parametri completamente non annotati e variabili locali senza specificare il tipo — tutto il codice si basava esclusivamente sull'inferenza dei tipi. Il team ha affrontato numerosi errori personalizzati e confusione: non era chiaro quali tipi ci si aspetta per i parametri e cosa restituisce realmente la funzione.
Vantaggi:
Svantaggi:
Un altro team ha utilizzato l'inferenza dei tipi solo per variabili locali in espressioni semplici, e per tutte le API pubbliche, strutture generiche e funzioni ha sempre specificato i tipi esplicitamente. Di conseguenza, il supporto e la comprensione del codice sono migliorati notevolmente, riducendo il numero di bug.
Vantaggi:
Svantaggi: