00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.