In Rust e non solo l'input/output, ovvero la capacità di un programma di emettere dati e riceverne dall'esterno, è spesso un aspetto vincolante affinché questo possa procedere con le proprie elaborazioni. Come in ogni corso di programmazione ne abbiamo già visto un primo esempio sin dall'inizio: la stampa delle stringhe in output. In tale caso abbiamo proceduto con una macro messa a disposizione dal linguaggio, println!
, ma adesso è arrivato il momento di entrare nei dettagli per comprendere il funzionamento di tutto questo sottosistema.
L'ambito che approcciamo è estremamente vasto a causa delle varie tipologie di input/output ed i diversi casi d'uso che esistono ma qui ci limiteremo ad una presentazione di orientamento seppure a 360 gradi.
Ricevere dati utente
Un primo caso molto utile per interagire con gli utenti è quello della ricezione di input che si esplicita nel chiedere dati all'utente che risponde in maniera interattiva. Per poter accedere a questo sottosistema facciamo riferimento al modulo std::io
nel quale troveremo stdin
, il canale dell'input nel sistema. Svolgiamo un rapido esempio per scrivere una semplice applicazione di saluto:
use std::io::stdin;
fn main(){
let mut nome = String::new();
println!("Come ti chiami?");
let _ = stdin().read_line(&mut nome);
println!("Ciao, {}. Benvenuto!", nome.trim());
}
Una volta importato stdin
il sistema resta in attesa di un messaggio in input dell'utente e, non appena lo ottiene, lo legge con read_line
. Ecco un esempio di sessione di interazione:
Come ti chiami?
Gianluca
Ciao, Gianluca. Benvenuto!
A parte quanto detto, non c'è null'altro di particolare da segnalare nell'esempio se non l'uso della funzione trim
per eliminare dal testo letto il carattere di a capo.
Sebbene potremmo non sentirne il bisogno grazie a println!
, ricordiamo che esiste anche un sistema di output con std::io
che offre accesso al canale stdout
.
Scrivere su file in Rust
Per quanto riguarda la scrittura su file, il percorso non è così differente. Accederemo all'oggetto use std::io::Write
che creerà un canale di comunicazione con il file destinazione che sarà definito mediante std::fs::File
. Proviamo - anche a titolo di ripasso - a riproporre l'esempio della stampa della tabellina in modo che l'ouput non sia stampato a console ma rediretto su file e che quest'ultimo abbia un nome personalizzato in base alla tabellina stampata. Ovviamente, approfittando di quanto visto nel primo paragrafo di questa lezione, sarà l'utente a scegliere di quale numero stampare la tabellina! Ecco qui:
use std::io::Write;
use std::fs::File;
use std::io::stdin;
fn main() {
let mut input = String::new();
println!("Immettere un numero intero:");
// leggiamo un numero in input
let _=stdin().read_line(&mut input).expect("Errore di lettura dell'input");
let tabellina_del:u8=input.trim().parse().expect("Non hai immesso un numero!");
// definiamo il file destinazione
let mut file = File::create(format!("tabellina_del_{}.txt", tabellina_del)).expect("Errore: creazione del file non riuscita!");
for contatore in 1..=10{
// creiamo una singola riga della tabellina
let riga= format!("{} X {} = {}\n", tabellina_del, contatore, tabellina_del*contatore);
// salviamo la riga su file
file.write_all(riga.as_bytes()).expect("Errore in scrittura");
}
println!("Fine del salvataggio del file");
}
Se, ad esempio, alla richiesta Immettere un numero intero: rispondessimo 7 otterremmo il file tabellina_del_7.txt
con il seguente contenuto:
7 X 1 = 7
7 X 2 = 14
7 X 3 = 21
7 X 4 = 28
7 X 5 = 35
7 X 6 = 42
7 X 7 = 49
7 X 8 = 56
7 X 9 = 63
7 X 10 = 70
Altro aspetto interessante è la macro format!
che si usa per formattare del testo in una nuova stringa usando gli stessi formati di println!
.
Leggere da file con Rust
La lettura da file è un argomento più vasto di quanto visto sinora e offre varie possibilità soprattutto in base alle dimensioni del file che potrebbero richiedere approcci ottimizzati. In questo punto, vedremo quello più comune e generalmente utilizzato in moltissimi casi. Con questo approccio Rust offre una certa duttilità in quanto permette di ricevere l'intero contenuto del file in una stringa e da qui iniziare ad analizzarla. Come si può immaginare procederemo in maniera analoga al caso precedente ovvero attingendo al modulo std::io
:
use std::io::Read;
fn main(){
let file = std::fs::File::open("tabellina_del_7.txt").unwrap();
let mut contenuto = String::new();
file.read_to_string(&mut contenuto).unwrap();
print!("{}", contenuto);
}
Il contenuto che verrà prodotto in output sarà quello già considerato prima e, come possiamo vedere, l'intero procedimento è in mano alla funzione read_to_string
che si preoccuperà di inserire tutto quello che c'è nel file all'interno della stringa contenuto
.