Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.