SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI
Gestione del Potenziale Fallimento con Result
Stiamo ancora lavorando su questa riga di codice. Stiamo ora discutendo una terza riga di testo, ma nota che fa ancora parte di una singola linea logica di codice. La parte successiva è questo metodo:
kotlin
.expect("Impossibile leggere la linea");
Avremmo potuto scrivere questo codice come:
io::stdin().read_line(&mut guess).expect(“Impossibile leggere la linea”);
Tuttavia, una sola riga lunga è difficile da leggere, quindi è meglio dividerla. Spesso è saggio introdurre una nuova riga e altri spazi per aiutare a suddividere le lunghe righe quando si chiama un metodo con la sintassi .nome_metodo(). Ora discutiamo cosa fa questa riga.
Come già menzionato, read_line inserisce ciò che l’utente inserisce nella stringa che passiamo ad essa, ma restituisce anche un valore Result. Result è un’enumerazione, spesso chiamata enum, che è un tipo che può trovarsi in uno dei molteplici stati possibili. Chiamiamo ciascuno stato possibile una variante.
Il capitolo 6 tratterà gli enum in modo più dettagliato. Lo scopo di questi tipi Result è codificare le informazioni di gestione degli errori.
Le varianti di Result sono Ok e Err. La variante Ok indica che l’operazione è stata eseguita con successo, e all’interno di Ok c’è il valore generato con successo. La variante Err significa che l’operazione è fallita, e Err contiene informazioni su come o perché l’operazione è fallita.
I valori del tipo Result, come i valori di qualsiasi tipo, hanno metodi definiti su di essi. Un’istanza di Result ha un metodo expect che puoi chiamare. Se questa istanza di Result è un valore Err, expect causerà il crash del programma e visualizzerà il messaggio che hai passato come argomento a expect. Se il metodo read_line restituisce un Err, probabilmente sarà il risultato di un errore proveniente dal sistema operativo sottostante. Se questa istanza di Result è un valore Ok, expect prenderà il valore di ritorno che Ok sta tenendo e ti restituirà solo quel valore in modo che tu possa utilizzarlo. In questo caso, quel valore è il numero di byte dell’input dell’utente.
Se non chiami expect, il programma verrà compilato, ma otterrai un avviso:
$ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) warning: unused Result
that must be used –> src/main.rs:10:5 | 10 | io::stdin().read_line(&mut guess); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this Result
may be an Err
variant, which should be handled = note: #[warn(unused_must_use)]
on by default
warning: guessing_game
(bin “guessing_game”) generated 1 warning Finished dev [unoptimized + debuginfo] target(s) in 0.59s
Rust avverte che non hai utilizzato il valore Result restituito da read_line, indicando che il programma non ha gestito un possibile errore.
Il modo corretto per eliminare l’avviso è scrivere effettivamente il codice di gestione degli errori, ma nel nostro caso vogliamo semplicemente far crashare questo programma quando si verifica un problema, quindi possiamo usare expect. Imparerai a recuperare gli errori nel Capitolo 9. Stampa dei Valori con gli Spaziatori di println!
Oltre alla parentesi graffa di chiusura, c’è solo un’altra riga da discutere nel codice finora:
arduino
println!("Hai indovinato: {guess}");
Questa riga stampa la stringa che ora contiene l’input dell’utente. L’insieme di parentesi graffe {} è un segnaposto: pensa a {} come a delle piccole chele di granchio che tengono in posizione un valore. Quando stampi il valore di una variabile, il nome della variabile può andare all’interno delle parentesi graffe. Quando stampi il risultato della valutazione di un’espressione, inserisci le parentesi graffe vuote nella stringa di formato, quindi segui la stringa di formato con un elenco separato da virgole di espressioni da stampare in ogni segnaposto di parentesi graffe vuote nello stesso ordine. Stampare una variabile e il risultato di un’espressione in una sola chiamata a println! sarebbe così:
let x = 5; let y = 10;
println!(“x = {x} e y + 2 = {}”, y + 2);
Questo codice stamperebbe x = 5 e y + 2 = 12. Test della Prima Parte
Testiamo la prima parte del gioco di indovinelli. Esegui il programma utilizzando cargo run:
$ cargo run Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finished dev [unoptimized + debuginfo] target(s) in 6.44s Running target/debug/guessing_game
Indovina il numero! Inserisci il tuo tentativo. 6 Hai indovinato: 6
In questo punto, la prima parte del gioco è completata: stiamo ottenendo input dalla tastiera e poi lo stiamo stampando. Generazione di un Numero Segreto
Successivamente, dobbiamo generare un numero segreto che l’utente cercherà di indovinare. Il numero segreto dovrebbe essere diverso ogni volta in modo che il gioco sia divertente da giocare più di una volta. Useremo un numero casuale tra 1 e 100 in modo che il gioco non sia troppo difficile. Rust non include ancora funzionalità di numeri casuali nella sua libreria standard. Tuttavia, il team di Rust fornisce una libreria rand con tali funzionalità. Utilizzo di una Libreria per Ottenere Più Funzionalità
Ricorda che una libreria è una collezione di file di codice sorgente Rust. Il progetto che abbiamo costruito è una libreria binaria, che è un eseguibile. La libreria rand è una libreria di libreria, che contiene codice destinato ad essere utilizzato in altri programmi e non può essere eseguita da sola.
Il coordinamento di crate esterni di Cargo è dove Cargo brilla davvero. Prima di poter scrivere del codice che utilizza rand, è necessario modificare il file Cargo.toml per includere la crate rand come dipendenza. Apri quel file ora e aggiungi la seguente riga in fondo, sotto l’intestazione della sezione [dependencies] che Cargo ha creato per te. Assicurati di specificare rand esattamente come abbiamo qui, con questo numero di versione, altrimenti gli esempi di codice in questo tutorial potrebbero non funzionare:
Nome file: Cargo.toml
[dependencies] rand = “0.8.5”
Nel file Cargo.toml, tutto ciò che segue un’intestazione fa parte di quella sezione che continua fino a quando non inizia un’altra sezione. In [dependencies] dici a Cargo di quali crate esterne dipende il tuo progetto e quali versioni di quelle crate richiedi. In questo caso, specifichiamo la crate rand con il specificatore di versione semantico 0.8.5. Cargo comprende la Versioning Semantica (a volte chiamata SemVer), che è uno standard per la scrittura di numeri di versione. Il specificatore 0.8.5 è in realtà un abbreviazione per ^0.8.5, il che significa qualsiasi versione che è almeno 0.8.5 ma inferiore a 0.9.0.
Cargo considera queste versioni come avere API pubbliche compatibili con la versione 0.8.5, e questa specifica garantisce che otterrai l’ultima versione di patch che verrà comunque compilata con il codice in questo capitolo. Nessuna versione 0.9.0 o superiore è garantita di avere la stessa API di quella usata dai seguenti esempi.
Ora, senza cambiare nulla del codice, costruiamo il progetto, come mostrato nel Listato 2-2.
$ cargo build Aggiornamento dell’indice di crates.io Scaricamento di rand v0.8.5 Scaricamento di libc v0.2.127 Scaricamento di getrandom v0.2.7 Scaricamento di cfg-if v1.0.0 Scaricamento di ppv-lite86 v0.2.16 Scaricamento di rand_chacha v0.3.1 Scaricamento di rand_core v0.6.3 Compilazione di libc v0.2.127 Compilazione di getrandom v0.2.7 Compilazione di cfg-if v1.0.0 Compilazione di ppv-lite86 v0.2.16 Compilazione di rand_core v0.6.3 Compilazione di rand_chacha v0.3.1 Compilazione di rand v0.8.5 Compilazione di guessing_game v0.1.0 (file:///projects/guessing_game) Finito dev [non ottimizzato + informazioni di debug] target(s) in 2.53s
Listato 2-2: L’output dall’esecuzione di cargo build dopo aver aggiunto la crate rand come dipendenza
Potresti vedere numeri di versione diversi (ma saranno tutti compatibili con il codice, grazie a SemVer!) e righe diverse (a seconda del sistema operativo), e le righe potrebbero essere in un ordine diverso.
Quando includiamo una dipendenza esterna, Cargo recupera le ultime versioni di tutto ciò di cui quella dipendenza ha bisogno dal registro, che è una copia dei dati da Crates.io. Crates.io è dove le persone nell’ecosistema Rust pubblicano i propri progetti Rust open source per essere utilizzati dagli altri.
Dopo l’aggiornamento del registro, Cargo controlla la sezione [dependencies] e scarica eventuali crate elencate che non sono già state scaricate. In questo caso, anche se abbiamo elencato solo rand come dipendenza, Cargo ha anche ottenuto altre crate di cui rand dipende per funzionare. Dopo aver scaricato le crate, Rust le compila e quindi compila il progetto con le dipendenze disponibili.
Se esegui immediatamente nuovamente cargo build senza apportare modifiche, non otterrai alcun output oltre alla riga Finito. Cargo sa di aver già scaricato e compilato le dipendenze e che non hai cambiato nulla su di esse nel file Cargo.toml. Cargo sa anche che non hai cambiato nulla nel tuo codice, quindi non lo ricompila nemmeno. Non avendo nulla da fare, esce semplicemente.
Se apri il file src/main.rs, apporti una modifica banale e quindi lo salvi e lo compil, vedrai solo due righe di output:
$ cargo build Compiling guessing_game v0.1.0 (file:///projects/guessing_game) Finito dev [non ottimizzato + informazioni di debug] target(s) in 2.53 secs
Queste righe mostrano che Cargo aggiorna solo la build con la tua piccola modifica al file src/main.rs. Le tue dipendenze non sono cambiate, quindi Cargo sa che può riutilizzare ciò che ha già scaricato e compilato per quelli. Assicurare compilazioni riproducibili con il file Cargo.lock
Cargo ha un meccanismo che garantisce di poter ricostruire lo stesso artefatto ogni volta che tu o chiunque altro compila il tuo codice: Cargo utilizzerà solo le versioni delle dipendenze che hai specificato fino a quando non indichi diversamente. Ad esempio, diciamo che la prossima settimana esce la versione 0.8.6 della crate rand, e quella versione contiene una correzione di bug importante, ma contiene anche una regressione che romperà il tuo codice. Per gestire questo, Rust crea il file Cargo.lock la prima volta che esegui cargo build, quindi ora lo abbiamo nella directory guessing_game.
Quando compili un progetto per la prima volta, Cargo individua tutte le versioni delle dipendenze che soddisfano i criteri e le scrive nel file Cargo.lock. Quando compili il tuo progetto in futuro, Cargo vedrà che esiste il file Cargo.lock e utilizzerà le versioni specificate lì anziché fare tutto il lavoro di individuazione delle versioni nuovamente. Questo ti consente di avere una build riproducibile automaticamente. In altre parole, il tuo progetto rimarrà a 0.8.5 fino a quando non lo aggiorni esplicitamente, grazie al file Cargo.lock. Poiché il file Cargo.lock è importante per le build riproducibili, viene spesso controllato nel controllo della versione insieme al resto del codice nel tuo progetto. Aggiornare una crate per ottenere una nuova versione
Quando vuoi aggiornare una crate, Cargo fornisce il comando update, che ignorerà il file Cargo.lock e individuerà tutte le ultime versioni che soddisfano le tue specifiche in Cargo.toml. Cargo scriverà quindi quelle versioni nel file Cargo.lock. In caso contrario, per impostazione predefinita, Cargo cercherà solo le versioni maggiori di 0.8.5 e minori di 0.9.0. Se la crate rand ha rilasciato le due nuove versioni 0.8.6 e 0.9.0, vedresti quanto segue se eseguissi cargo update:
$ cargo update Aggiornamento dell’indice di crates.io Aggiornamento di rand v0.8.5 -> v0.8.6
Cargo ignora il rilascio 0.9.0. A questo punto, noteresti anche una modifica nel tuo file Cargo.lock che indica che la versione della crate rand che stai usando è la 0.8.6. Per utilizzare la versione rand 0.9.0 o qualsiasi versione della serie 0.9.x, dovresti aggiornare il file Cargo.toml in questo modo:
[dependencies] rand = “0.9.0”
La prossima volta che esegui cargo build, Cargo aggiornerà il registro delle crate disponibili e riesaminerà i tuoi requisiti rand secondo la nuova versione che hai specificato.
C’è molto altro da dire su Cargo e sul suo ecosistema, di cui parleremo nel Capitolo 14, ma per ora, questo è tutto ciò che devi sapere. Cargo rende molto facile riutilizzare le librerie, quindi gli sviluppatori Rust possono scrivere progetti più piccoli che sono composti da un certo numero di pacchetti. Generare un numero casuale
Iniziamo a utilizzare rand per generare un numero da indovinare. Il prossimo passo è aggiornare src/main.rs, come mostrato nel Listato 2-3.
Nome file: src/main.rs
use std::io; use rand::Rng;
fn main() { println!(“Indovina il numero!”);
rust
let numero_segreto = rand::thread_rng().gen_range(1..=100);
println!("Il numero segreto è: {numero_segreto}"); println!("Per favore inserisci il tuo tentativo."); let mut tentativo = String::new(); io::stdin()
.read_line(&mut tentativo)
.expect("Impossibile leggere la riga");
println!("Hai indovinato: {tentativo}");
}
Listato 2-3: Aggiunta del codice per generare un numero casuale
Prima aggiungiamo la linea use rand::Rng;. Il trait Rng definisce i metodi che implementano i generatori di numeri casuali, e questo trait deve essere nello scope per poter utilizzare quei metodi. Il Capitolo 10 tratterà i trait in dettaglio.
Successivamente, aggiungiamo due linee nel mezzo. Nella prima riga, chiamiamo la funzione rand::thread_rng che ci fornisce il particolare generatore di numeri casuali che stiamo per utilizzare: uno che è locale al thread corrente di esecuzione e che è seminato dal sistema operativo. Quindi chiamiamo il metodo gen_range sul generatore di numeri casuali. Questo metodo è definito dal trait Rng che abbiamo introdotto nello scope con la dichiarazione use rand::Rng;. Il metodo gen_range prende un’espressione di range come argomento e genera un numero casuale nell’intervallo. Il tipo di espressione di range che stiamo usando qui ha la forma start..=end ed è inclusivo sui limiti inferiore e superiore, quindi dobbiamo specificare 1..=100 per richiedere un numero compreso tra 1 e 100.
rust
Nota: Non saprai solo quali trait usare e quali metodi e funzioni chiamare da una crate, quindi ciascuna crate ha una documentazione con istruzioni per l'uso. Un'altra caratteristica interessante di Cargo è che l'esecuzione del comando cargo doc --open compilerà la documentazione fornita da tutte le tue dipendenze in locale e la aprirà nel tuo browser. Se sei interessato ad altre funzionalità della crate rand, ad esempio, esegui cargo doc --open e fai clic su rand nella barra laterale a sinistra.
La seconda nuova riga stampa il numero segreto. Questo è utile mentre stiamo sviluppando il programma per poterlo testare, ma lo elimineremo dalla versione finale. Non è un gran gioco se il programma stampa la risposta appena inizia!
Prova ad eseguire il programma alcune volte:
$ cargo run Compilazione di guessing_game v0.1.0 (file:///projects/guessing_game) Finito dev [non ottimizzato + informazioni di debug] target(s) in 2.53s Running target/debug/guessing_game
Indovina il numero! Il numero segreto è: 62 Per favore inserisci il tuo tentativo.
Inserisci il tuo tentativo 12 Hai indovinato: 12
$ cargo run Compilazione di guessing_game v0.1.0 (file:///projects/guessing_game) Finito dev [non ottimizzato + informazioni di debug] target(s) in 2.53s Running target/debug/guessing_game
Indovina il numero! Il numero segreto è: 33 Per favore inserisci il tuo tentativo.
Inserisci il tuo tentativo 44 Hai indovinato: 44
$ cargo run Compilazione di guessing_game v0.1.0 (file:///projects/guessing_game) Finito dev [non ottimizzato + informazioni di debug] target(s) in 2.53s Running target/debug/guessing_game
Indovina il numero! Il numero segreto è: 47 Per favore inserisci il tuo tentativo.
Inserisci il tuo tentativo 22 Hai indovinato: 22
$ cargo run Compilazione di guessing_game v0.1.0 (file:///projects/guessing_game) Finito dev [non ottimizzato + informazioni di debug] target(s) in 2.53s Running target/debug/guessing_game
Indovina il numero! Il numero segreto è: 41 Per favore inserisci il tuo tentativo.
Inserisci il tuo tentativo 7 Hai indovinato: 7
Quello che fai in questo momento è chiamato test di sviluppo. Questo è diverso dal test di unità e dal test di integrazione che scriverai in seguito. Stai verificando il funzionamento del tuo programma come lo sviluppi. In questo caso, vuoi vedere che il programma genera numeri casuali nell’intervallo corretto, e vuoi assicurarti che l’input dell’utente funzioni. Più avanti, nel Capitolo 11, parleremo dei test automatizzati e dei test driven development (TDD), che ti permetteranno di scrivere test per i tuoi programmi che il computer può eseguire per te. Rimozione della stampa del numero segreto
Ora che il nostro programma funziona come previsto, rimuoviamo la stampa del numero segreto. Sostituisci la riga println!(“Il numero segreto è: {numero_segreto}”); con il commento // println!(“Il numero segreto è: {numero_segreto}”);.
Rimuoviamo anche il commento // dal println!(“Hai indovinato: {tentativo}”); per vedere l’input dell’utente quando lo eseguiamo.
Ecco il tuo nuovo codice, pronto per il successivo passaggio:
rust
use std::io;
use rand::Rng; fn main() {
println!("Indovina il numero!"); let numero_segreto = rand::thread_rng().gen_range(1..=100); // println!("Il numero segreto è: {numero_segreto}"); println!("Per favore inserisci il tuo tentativo."); let mut tentativo = String::new(); io::stdin()
.read_line(&mut tentativo)
.expect("Impossibile leggere la riga");
println!("Hai indovinato: {tentativo}");
}
Ora che abbiamo generato un numero segreto, stiamo quasi finendo con la prima parte del gioco. Prossimo passo sarà acquisire l’input dell’utente, confrontarlo con il numero segreto e informare l’utente se ha indovinato correttamente o meno.