Variabili e mutabilità
Come accennato nella sezione “Memorizzare i valori con le Variabili”, come impostazione predefinita, le variabili sono immutabili. Questo è uno dei tanti stimoli che Rust ti dà per scrivere il tuo codice in modo da sfruttare la sicurezza e la facilità di concorrenza che Rust offre. Tuttavia, hai ancora la possibilità di rendere le tue variabili mutabili. Esploriamo come e perché Rust ti incoraggia a favorire l’immutabilità e perché a volte potresti voler rinunciare a questa cosa.
Quando una variabile è immutabile, una volta che un valore è legato a un nome,
non è più possibile cambiarlo. Per vederlo con mano, genera un nuovo progetto
chiamato variabili nella tua cartella progetti utilizzando cargo new variabili
.
Ed ora, nella nuova cartella variabili, apri src/main.rs e sostituisci il suo codice con il seguente, che per il momento risulterà non compilabile:
File: src/main.rs
fn main() {
let x = 5;
println!("Il valore di x è: {x}");
x = 6;
println!("Il valore di x è: {x}");
}
Salva ed esegui il programma utilizzando cargo run
. Dovresti ricevere un
messaggio di errore relativo a un errore di immutabilità, come mostrato in
questo output:
$ cargo run
Compiling variabili v0.1.0 (file:///progetti/variabili)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("Il valore di x è: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variabili` (bin "variabili") due to 1 previous error
Questo esempio mostra come il compilatore ti aiuta a trovare gli errori nei tuoi programmi. Gli errori del compilatore possono essere frustranti, ma in realtà significano solo che il tuo programma non sta ancora facendo in modo sicuro ciò che vuoi; non significano che non sei un buon programmatore! Anche ai Rustaceani più esperti appaiono errori del compilatore.
Hai ricevuto il messaggio di errore cannot assign twice to immutable variable
perché hai cercato di assegnare un secondo valore alla variabile immutabile
x
.
È importante che ci vengano segnalati errori in tempo di compilazione quando si tenta di modificare un valore che è stato definito immutabile, perché proprio questa situazione può portare a dei bug. Se una parte del nostro codice opera sulla base del presupposto che un valore non cambierà mai e un’altra parte del codice modifica quel valore, è possibile che la prima parte del codice non faccia ciò per cui è stata progettata. La causa di questo tipo di bug può essere difficile da rintracciare a posteriori, soprattutto quando la seconda parte del codice modifica il valore solo qualche volta. Il compilatore di Rust garantisce che quando si afferma che un valore non cambierà, non cambierà davvero, quindi non dovrai tenerne traccia tu stesso. Il tuo codice sarà quindi più facile da analizzare.
Ma la mutabilità può essere molto utile e può rendere il codice più comodo da
scrivere. Sebbene le variabili siano immutabili come impostazione predefinita,
puoi renderle mutabili aggiungendo mut
davanti al nome della variabile, come
hai fatto nel Capitolo 2. L’aggiunta di mut
rende anche palese quando si andrà a rileggere il
codice in futuro che altre parti del codice cambieranno il valore di questa
variabile.
Ad esempio, cambiamo src/main.rs con il seguente:
File: src/main.rs
fn main() { let mut x = 5; println!("Il valore di x è: {x}"); x = 6; println!("Il valore di x è: {x}"); }
Quando eseguiamo il programma ora, otteniamo questo:
$ cargo run
Compiling variabili v0.1.0 (file:///progetti/variabili)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/variabili`
Il valore di x è: 5
Il valore di x è: 6
Siamo autorizzati a cambiare il valore legato a x
da 5
a 6
quando si usa
mut
. In definitiva, decidere se usare la mutabilità o meno dipende da te e da
ciò che ritieni più utile in quella particolare situazione.
Costanti
Come le variabili immutabili, le costanti sono valori legati a un nome che non possono essere modificati, ma ci sono alcune differenze tra le costanti e le variabili.
Innanzitutto, non puoi usare mut
con le costanti. Le costanti non solo sono
immutabili come impostazione predefinita, sono sempre immutabili. Dichiari le
costanti usando la parola chiave const
invece della parola chiave let
e il
type del valore deve essere annotato. Tratteremo i type e le annotazioni
dei type nella prossima sezione, “Datatype - Tipi di dato”, quindi non preoccuparti dei dettagli in questo momento. Sappi solo
che devi sempre annotare il type.
Le costanti possono essere dichiarate in qualsiasi scope, compreso quello globale, il che le rende utili per i valori che molte parti del codice devono conoscere.
L’ultima differenza è che le costanti possono essere impostate solo su un’espressione costante, non sul risultato di un valore che può essere calcolato solo in fase di esecuzione.
Ecco un esempio di dichiarazione di una costante:
#![allow(unused)] fn main() { const TRE_ORE_IN_SECONDI: u32 = 60 * 60 * 3; }
Il nome della costante è TRE_ORE_IN_SECONDI
e il suo valore è impostato come
il risultato della moltiplicazione di 60 (il numero di secondi in un minuto) per
60 (il numero di minuti in un’ora) per 3 (il numero di ore che vogliamo contare
in questo programma). La convenzione di Rust per la denominazione delle costanti
prevede l’uso di maiuscole con trattini bassi tra le parole. Il compilatore è in
grado di valutare il risultato di un’operazione in fase di compilazione, il che
ci permette di scegliere di scrivere questo valore in un modo più facile da
capire e da verificare, piuttosto che impostare questa costante al valore
10.800. Consulta la sezione Valutazione delle costanti (in
inglese) per maggiori informazioni sulle operazioni che possono
essere utilizzate quando si dichiarano le costanti.
Le costanti sono valide per tutto il tempo di esecuzione di un programma, all’interno dello scope in cui sono state dichiarate. Questa proprietà rende le costanti utili per dei valori nella tua applicazione che più parti del programma potrebbero avere bisogno di conoscere, come ad esempio il numero massimo di punti che un giocatore di un gioco può guadagnare o la velocità della luce.
Dichiarare come costanti i valori codificati usati nel tuo programma è utile per trasmettere il significato di quel valore a chi leggerà il codice in futuro. Inoltre, è utile per avere un solo punto del codice da modificare se il valore codificato deve essere aggiornato in futuro.
Shadowing
Come hai visto nel tutorial sul gioco dell’indovinello nel Capitolo
2, puoi dichiarare
una nuova variabile con lo stesso nome di una variabile precedente. I Rustaceani
dicono che la prima variabile è messa in ombra, shadowing, dalla seconda, il
che significa che la seconda variabile è quella che il compilatore vedrà quando
userai il nome della variabile. In effetti, la seconda variabile mette in ombra
la prima, portando a sé qualsiasi uso del nome della variabile fino a quando non
sarà essa stessa messa in ombra o lo scope terminerà. Possiamo fare
shadowing di una variabile usando lo stesso nome della variabile e ripetendo
l’uso della parola chiave let
come segue:
File: src/main.rs
fn main() { let x = 5; let x = x + 1; { let x = x * 2; println!("Il valore di x nello scope interno è: {x}"); } println!("Il valore di x è: {x}"); }
Questo programma vincola innanzitutto x
a un valore di 5
. Poi crea una nuova
variabile x
ripetendo let x =
, prendendo il valore originale e aggiungendo
1
in modo che il valore di x
sia 6
. Quindi, all’interno di uno scope
interno creato con le parentesi graffe, la terza istruzione let
mette in
ombra x
e crea una nuova variabile, moltiplicando il valore precedente per
2
per dare a x
un valore di 12
. Quando lo scope termina, finisce pure lo
shadowing e x
torna a essere 6
. Quando si esegue questo programma, si
ottiene il seguente risultato:
$ cargo run
Compiling variabili v0.1.0 (file:///progetti/variabili)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/variabili`
Il valore di x nello scope interno è: 12
Il valore di x è: 6
Lo shadowing è diverso dall’indicare una variabile come mut
perché otterremo
un errore in fase di compilazione se cerchassimo accidentalmente di riassegnare
questa variabile senza usare la parola chiave let
. Usando let
, possiamo
eseguire alcune trasformazioni su un valore ma far sì che la variabile sia
immutabile dopo che le trasformazioni sono state completate.
L’altra differenza tra mut
e lo shadowing è che, poiché stiamo
effettivamente creando una nuova variabile quando usiamo di nuovo la parola
chiave let
, possiamo cambiare il type del valore ma riutilizzare lo stesso
nome. Ad esempio, supponiamo che il nostro programma chieda a un utente di
scriverci quanti spazi vuole tra un testo e l’altro inserendo dei caratteri di
spazio, e poi vogliamo memorizzare questo input come un numero:
fn main() { let spazi = " "; let spazi = spazi.len(); }
La prima variabile spazi
è di type stringa e la seconda variabile spazi
è
di type numerico. Lo shadowing ci evita quindi di dover inventare nomi
diversi, come spazi_str
e spazi_num
; possiamo invece riutilizzare il nome
più semplice spazi
. Tuttavia, se proviamo a usare mut
per fare questa cosa,
come mostrato qui, otterremo un errore di compilazione:
fn main() {
let mut spazi = " ";
spazi = spazi.len();
}
L’errore dice che non è consentito mutare il type di una variabile:
$ cargo run
Compiling variabili v0.1.0 (file:///progetti/variabili)
error[E0308]: mismatched types
--> src/main.rs:4:13
|
3 | let mut spazi = " ";
| ----- expected due to this value
4 | spazi = spazi.len();
| ^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variabili` (bin "variabili") due to 1 previous error
Ora che abbiamo visto il funzionamento delle variabili, passiamo in rassegna le varie tipologie di dato, type, che possono essere.