write! și writeln! în Rust

Scriu text formatat într-o destinație aleasă: un String, un fișier, stdout sau Formatter-ul din Display. Returnează un Result.

Sintaxă
use std::fmt::Write;       // pentru String
use std::io::Write;        // pentru fișiere, stdout, Vec<u8>
write!(destinatie, "{}", valoare)?;
writeln!(destinatie, "{}", valoare)?;   // cu rând nou

Ce face write! și writeln!

write!(dest, ...) și writeln!(dest, ...) au aceeași sintaxă ca print! și println!, dar scriu în dest. Destinația trebuie să implementeze fie std::fmt::Write (de exemplu String), fie std::io::Write (fișiere, stdout(), Vec<u8>, BufWriter). Trait-ul potrivit trebuie importat cu use.

Spre deosebire de println!, ele returnează un Result, pentru că scrierea poate eșua (disc plin, conexiune închisă). Îl tratezi cu ?, cu unwrap() sau îl verifici; dacă îl ignori, compilatorul avertizează unused `Result` that must be used.

În implementarea trait-ului Display (fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result), write!(f, ...) este modul obișnuit de a produce textul. Pentru a adăuga mult text într-un String, write! este mai eficient decât format! repetat, pentru că nu creează texte intermediare.

Ce returnează

fmt::Result (pentru fmt::Write) sau io::Result<()> (pentru io::Write).

Exemple

Exemplul 1: Construirea unui raport într-un String

Cu std::fmt::Write importat, String poate fi destinația lui writeln!. Textul crește pe loc, fără texte intermediare.

use std::fmt::Write;

fn main() {
    let elevi = [("Ana", 9.45), ("Mihai", 8.1), ("Ioana", 9.9)];
    let mut raport = String::new();
    writeln!(raport, "Raport clasa a X-a").unwrap();
    for (i, (nume, media)) in elevi.iter().enumerate() {
        writeln!(raport, "{}. {nume:<6} {media:.2}", i + 1).unwrap();
    }
    write!(raport, "Total elevi: {}", elevi.len()).unwrap();
    println!("{raport}");
}

Rezultat

Raport clasa a X-a
1. Ana    9.45
2. Mihai  8.10
3. Ioana  9.90
Total elevi: 3

Exemplul 2: Scrierea într-un fișier, cu ?

Cu std::io::Write, destinația este un fișier creat în directorul curent. Fiecare writeln! poate eșua, deci folosim ?, iar main returnează io::Result.

use std::fs::{self, File};
use std::io::{self, BufWriter, Write};

fn main() -> io::Result<()> {
    let mut fisier = BufWriter::new(File::create("temperaturi.csv")?);
    writeln!(fisier, "oras,grade")?;
    for (oras, grade) in [("Iași", 21.5), ("Suceava", 18.0)] {
        writeln!(fisier, "{oras},{grade}")?;
    }
    fisier.flush()?;

    print!("{}", fs::read_to_string("temperaturi.csv")?);
    Ok(())
}

Rezultat

oras,grade
Iași,21.5
Suceava,18

Exemplul 3: write! în implementarea lui Display

f este destinația dată de println!. Ce scrie write! în f apare acolo unde este folosit {}. Opțiunile de formatare se pot transmite mai departe ({:02}).

use std::fmt;

struct Ora {
    ore: u8,
    minute: u8,
}

impl fmt::Display for Ora {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:02}:{:02}", self.ore, self.minute)
    }
}

fn main() {
    let tren = Ora { ore: 7, minute: 5 };
    println!("Trenul Iași–București pleacă la {tren}");
    let text = format!("[{tren}]");
    println!("{text}");
}

Rezultat

Trenul Iași–București pleacă la 07:05
[07:05]

Greșeli frecvente

  • Uiți să imporți trait-ul: cannot write into `String` cu sugestia use std::fmt::Write. Pentru fișiere ai nevoie de std::io::Write.
  • Imporți ambele trait-uri Write în același fișier: conflict de nume. Folosește use std::fmt::Write as FmtWrite; sau use std::io::{self, Write}; și std::fmt::Write prin cale completă.
  • Ignori rezultatul lui write! pe un fișier: o eroare de scriere trece neobservată. Folosește ?.
  • Folosești println! în fmt în loc de write!(f, ...): textul ajunge direct pe ecran, nu în locul unde a fost cerut (de exemplu într-un format!).

Comenzi înrudite

  • format! (și sintaxa de formatare): Construiește un String nou din șablon și valori, cu aceeași sintaxă ca println!, dar fără să afișeze nimic.
  • println! și print!: Afișează text pe ieșirea standard: println! adaugă la final un rând nou, print! nu. Acoladele {} sunt înlocuite cu valori.
  • format_args!: Pregătește un text formatat fără să-l construiască: produce un fmt::Arguments care poate fi scris oriunde, fără alocare.
  • operatorul ?: Pus după un Result sau Option: dă valoarea dacă totul e bine, altfel returnează imediat eroarea (sau None) din funcția curentă.
  • use: Aduce în domeniu un nume dintr-un modul sau crate (std::collections::BTreeMap), ca să-l poți folosi fără calea completă.