Input e Output
Il modulo std::io è il cuore delle operazioni di input e output in Rust. Fornisce trait, tipi e funzioni per leggere e scrivere dati in modo efficiente e sicuro, dalla console ai file e oltre.
Stampa con println! e print!
Le macro println! e print! sono il modo principale per scrivere su stdout:
fn main() {
print!("Senza a capo");
println!(" - Con a capo");
let nome = "Rust";
let versione = 2024;
println!("Linguaggio: {}, Edizione: {}", nome, versione);
}
println! aggiunge automaticamente un newline (\n) alla fine, mentre print! no.
Stringhe di Formato
Rust supporta una formattazione ricca e flessibile:
fn main() {
// Posizionale
println!("{0} viene prima di {1}, ma {0} è il migliore", "Rust", "Go");
// Named arguments
println!("{linguaggio} è fantastico!", linguaggio = "Rust");
// Formattazione numerica
println!("Decimale: {}", 42);
println!("Binario: {:b}", 42);
println!("Ottale: {:o}", 42);
println!("Esadecimale: {:x}", 42);
println!("Esadecimale maiuscolo: {:X}", 42);
// Padding e allineamento
println!("[{:>10}]", "destra"); // [ destra]
println!("[{:<10}]", "sinistra"); // [sinistra ]
println!("[{:^10}]", "centro"); // [ centro ]
println!("[{:*>10}]", "riempi"); // [****riempi]
// Precisione per i float
println!("Pi greco: {:.4}", 3.14159265); // Pi greco: 3.1416
println!("Scientifico: {:e}", 1000.0); // Scientifico: 1e3
// Debug output
let vettore = vec![1, 2, 3];
println!("Debug: {:?}", vettore);
println!("Debug formattato: {:#?}", vettore);
}
Altre Macro di Formattazione
Oltre a println!, esistono macro correlate:
fn main() {
// format! - restituisce una String
let s = format!("Il risultato è {}", 42);
println!("{}", s);
// eprintln! e eprint! - scrivono su stderr
eprintln!("Errore: qualcosa è andato storto");
// write! e writeln! - scrivono su qualsiasi Writer
use std::fmt::Write;
let mut buffer = String::new();
writeln!(buffer, "Riga 1").unwrap();
writeln!(buffer, "Riga 2").unwrap();
println!("{}", buffer);
}
Leggere da stdin con read_line
Per leggere l’input dell’utente si usa stdin().read_line():
use std::io;
fn main() {
println!("Come ti chiami?");
let mut nome = String::new();
io::stdin()
.read_line(&mut nome)
.expect("Errore nella lettura");
// trim() rimuove il newline finale
let nome = nome.trim();
println!("Ciao, {}!", nome);
}
Lettura con Parsing del Tipo
Spesso si vuole convertire l’input in un tipo numerico:
use std::io;
fn main() {
println!("Inserisci un numero:");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
let numero: i32 = match input.trim().parse() {
Ok(n) => n,
Err(_) => {
println!("Non è un numero valido!");
return;
}
};
println!("Il doppio è: {}", numero * 2);
}
stdin con Lock per Letture Multiple
Per leggere molte righe efficientemente, si usa stdin().lock() che evita di acquisire il lock ad ogni lettura:
use std::io::{self, BufRead};
fn main() {
println!("Inserisci righe di testo (Ctrl+D per terminare):");
let stdin = io::stdin();
let locked = stdin.lock();
for (i, riga) in locked.lines().enumerate() {
let riga = riga.unwrap();
println!("Riga {}: {}", i + 1, riga);
}
}
BufReader per Letture Bufferizzate
BufReader avvolge un reader e aggiunge un buffer interno, riducendo le chiamate di sistema:
use std::io::{self, BufRead, BufReader};
fn main() {
let stdin = io::stdin();
let reader = BufReader::new(stdin.lock());
let mut totale = 0;
let mut conteggio = 0;
for riga in reader.lines() {
let riga = riga.unwrap();
if riga.is_empty() {
break;
}
if let Ok(n) = riga.trim().parse::<i32>() {
totale += n;
conteggio += 1;
}
}
if conteggio > 0 {
println!("Media: {}", totale as f64 / conteggio as f64);
}
}
BufWriter per Scritture Bufferizzate
BufWriter raccoglie le scritture in un buffer e le invia in blocco, migliorando le prestazioni:
use std::io::{self, BufWriter, Write};
fn main() {
let stdout = io::stdout();
let mut writer = BufWriter::new(stdout.lock());
for i in 0..100 {
writeln!(writer, "Riga numero {}", i).unwrap();
}
// Il buffer viene svuotato automaticamente al drop,
// oppure manualmente con flush()
writer.flush().unwrap();
}
Il Trait Write
Il trait std::io::Write definisce l’interfaccia per scrivere byte:
use std::io::Write;
fn scrivi_su<W: Write>(writer: &mut W, dati: &[&str]) -> std::io::Result<()> {
for elemento in dati {
writeln!(writer, "- {}", elemento)?;
}
writer.flush()?;
Ok(())
}
fn main() {
let mut output = Vec::new(); // Vec<u8> implementa Write
scrivi_su(&mut output, &["Rust", "è", "fantastico"]).unwrap();
println!("{}", String::from_utf8(output).unwrap());
}
io::Result e Gestione degli Errori
Le operazioni di I/O restituiscono io::Result<T>, un alias per Result<T, io::Error>:
use std::io;
fn leggi_numero() -> io::Result<i32> {
let mut input = String::new();
io::stdin().read_line(&mut input)?; // l'operatore ? propaga l'errore
input.trim()
.parse::<i32>()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
fn main() {
match leggi_numero() {
Ok(n) => println!("Numero letto: {}", n),
Err(e) => eprintln!("Errore: {}", e),
}
}
Conclusione
Il modulo std::io di Rust offre un sistema completo e flessibile per l’input e output. Le macro println! e format! permettono una formattazione potente, stdin().read_line() gestisce l’input utente, e i tipi BufReader/BufWriter ottimizzano le prestazioni per operazioni I/O intensive. Grazie ai trait Read e Write, il codice può essere generico e funzionare con qualsiasi sorgente o destinazione di dati.