Variabili in Rust
Le variabili in Rust funzionano in modo diverso rispetto alla maggior parte degli altri linguaggi di programmazione. Per impostazione predefinita, tutte le variabili sono immutabili, una scelta progettuale che favorisce la sicurezza e la prevedibilitĂ del codice. In questa guida esploreremo come dichiarare e utilizzare le variabili in Rust.
ImmutabilitĂ per Default con let
In Rust, le variabili vengono dichiarate con la parola chiave let e sono immutabili per default:
fn main() {
let x = 5;
println!("Il valore di x è: {}", x);
// x = 10; // Errore! Non è possibile assegnare un nuovo valore
// a una variabile immutabile
}
Se si tenta di modificare una variabile immutabile, il compilatore genera un errore chiaro:
// error[E0384]: cannot assign twice to immutable variable `x`
Questa scelta progettuale incoraggia la scrittura di codice più sicuro, poiché il compilatore garantisce che i valori non cambino inaspettatamente.
MutabilitĂ con let mut
Quando è necessario modificare il valore di una variabile, si usa let mut:
fn main() {
let mut contatore = 0;
println!("Contatore iniziale: {}", contatore);
contatore = 1;
println!("Contatore dopo incremento: {}", contatore);
contatore += 1;
println!("Contatore dopo secondo incremento: {}", contatore);
}
La parola chiave mut comunica esplicitamente l’intenzione di modificare la variabile, rendendo il codice più leggibile e le intenzioni del programmatore più chiare.
Variable Binding
In Rust, la dichiarazione di una variabile viene chiamata binding (legame). Il binding associa un nome a un valore:
fn main() {
let nome = "Marco"; // Binding di una stringa
let eta = 25; // Binding di un intero
let attivo = true; // Binding di un booleano
println!("{} ha {} anni, attivo: {}", nome, eta, attivo);
}
Il binding in Rust implica anche il concetto di ownership (proprietĂ ): quando si fa il binding di un valore a una variabile, quella variabile diventa proprietaria del valore.
Annotazione del Tipo
Rust è un linguaggio con type inference (inferenza dei tipi): il compilatore è spesso in grado di dedurre il tipo di una variabile dal valore assegnato. Tuttavia, è possibile specificare esplicitamente il tipo:
fn main() {
let x: i32 = 42; // Intero a 32 bit con segno
let pi: f64 = 3.14159; // Numero in virgola mobile a 64 bit
let attivo: bool = true; // Booleano
let lettera: char = 'A'; // Carattere Unicode
println!("x = {}, pi = {}, attivo = {}, lettera = {}", x, pi, attivo, lettera);
}
L’annotazione del tipo è obbligatoria quando il compilatore non riesce a inferire il tipo, ad esempio con le costanti o in situazioni ambigue:
fn main() {
// Senza annotazione, il compilatore non sa quale tipo numerico usare
let indovinato: u32 = "42".parse().expect("Non è un numero!");
println!("Valore: {}", indovinato);
}
Variabili Non Utilizzate con _
Se una variabile non viene utilizzata, il compilatore Rust genera un warning. Per sopprimere questo avviso, si può prefissare il nome con un underscore _:
fn main() {
let _risultato_temporaneo = calcola_qualcosa();
// Nessun warning anche se _risultato_temporaneo non viene usato
let _ = 42; // Ignora completamente il valore
}
fn calcola_qualcosa() -> i32 {
42
}
L’underscore singolo _ è un pattern speciale che non fa nessun binding: il valore viene scartato immediatamente. Invece, _nome crea comunque un binding, ma sopprime il warning.
Shadowing
Lo shadowing (oscuramento) permette di ridichiarare una variabile con lo stesso nome, creando di fatto una nuova variabile che “oscura” la precedente:
fn main() {
let x = 5;
println!("x = {}", x); // Stampa: x = 5
let x = x + 1;
println!("x = {}", x); // Stampa: x = 6
let x = x * 2;
println!("x = {}", x); // Stampa: x = 12
}
Lo shadowing funziona anche all’interno di blocchi di codice:
fn main() {
let x = 10;
{
let x = x + 5; // Shadowing all'interno del blocco
println!("x nel blocco interno: {}", x); // Stampa: 15
}
println!("x nel blocco esterno: {}", x); // Stampa: 10
}
Shadowing con Cambio di Tipo
Una caratteristica potente dello shadowing è che permette di cambiare il tipo della variabile mantenendo lo stesso nome:
fn main() {
let spazi = " "; // tipo &str
let spazi = spazi.len(); // tipo usize
println!("Numero di spazi: {}", spazi); // Stampa: 3
}
Questo è particolarmente utile quando si vuole trasformare un valore senza dover inventare nomi diversi come spazi_str e spazi_num.
Differenza tra Shadowing e mut
Shadowing e mutabilitĂ sono concetti distinti con comportamenti differenti:
fn main() {
// Con mut: stessa variabile, stesso tipo, valore modificabile
let mut a = 5;
a = 10; // OK: stesso tipo (i32)
// a = "ciao"; // Errore! Non si può cambiare il tipo
// Con shadowing: nuova variabile, tipo può cambiare
let b = 5; // tipo i32
let b = "ciao"; // tipo &str - OK con shadowing
println!("a = {}, b = {}", a, b);
}
Le differenze principali sono:
mutpermette di modificare il valore ma non il tipo della variabile.- Shadowing crea una nuova variabile con lo stesso nome, permettendo di cambiare sia il valore che il tipo.
- Shadowing produce una variabile immutabile per default (a meno che non si usi anche
let mut).
fn main() {
// Shadowing è utile per trasformazioni sequenziali
let input = " 42 ";
let input = input.trim(); // &str senza spazi
let input: i32 = input.parse().unwrap(); // i32
println!("Valore finale: {}", input); // Stampa: 42
}
Conclusione
Il sistema di variabili di Rust è progettato per incoraggiare la scrittura di codice sicuro e prevedibile. L’immutabilità per default forza il programmatore a essere esplicito sulle proprie intenzioni, mentre lo shadowing offre flessibilità senza sacrificare la sicurezza. Comprendere la differenza tra mutabilità e shadowing è fondamentale per scrivere codice Rust idiomatico e sfruttare appieno le garanzie offerte dal compilatore.