Common Standard Macros

write! and writeln!

The write! and writeln! macros stream formatted output into any destination buffer or stream as its first argument (&mut dst), returning a Result<(), Error> rather than allocating a new String or panicking on I/O failure.

Two Target Traits: std::io::Write vs. std::fmt::Write

write!(dst, "...") and writeln!(dst, "...") work with any dst that implements either of the two Write traits in the standard library (whichever is in scope):

Target Trait Typical Destinations (dst) Return Type of write!(dst, ...) Can It Fail at Runtime?
std::io::Write File, TcpStream, BufWriter<W>, Vec<u8>, io::StdoutLock std::io::Result<()> Yes (disk full, broken pipe, socket closed)
std::fmt::Write String, std::fmt::Formatter<'_> std::fmt::Result (Result<(), fmt::Error>) No when writing to a String (unwrap() is infallible)
write!(dst, "key={val}")
  │
  ├── If `dst` implements `std::io::Write`  ──> writes UTF-8 bytes (`&[u8]`) -> `io::Result<()>`
  └── If `dst` implements `std::fmt::Write` ──> appends `&str` to `String`   -> `fmt::Result`

1. Writing UTF-8 Bytes to an I/O Stream or Vec<u8> (std::io::Write)

Because Vec<u8> implements std::io::Write, write! and writeln! can format text directly into a byte buffer or file and propagate I/O errors with ?:

use std::io::{self, Write};

fn encode_http_header(buf: &mut Vec<u8>, name: &str, value: &str) -> io::Result<()> {
    write!(buf, "{name}: {value}\r\n")
}

fn main() -> io::Result<()> {
    let mut packet: Vec<u8> = Vec::new();
    writeln!(packet, "GET /status HTTP/1.1")?;
    encode_http_header(&mut packet, "Host", "example.com")?;

    print!("{}", String::from_utf8_lossy(&packet));
    Ok(())
}
GET /status HTTP/1.1
Host: example.com

2. Appending Formatted Text to a String (std::fmt::Write)

Importing std::fmt::Write lets you append formatted values directly onto an existing String without allocating temporary format! strings:

use std::fmt::Write;

fn build_report(metrics: &[(&str, f64)]) -> String {
    let mut report = String::with_capacity(64);
    writeln!(&mut report, "=== Metrics ===").unwrap();

    for &(name, val) in metrics {
        writeln!(&mut report, "  {name:<10}: {val:>6.2}").unwrap();
    }
    report
}

fn main() {
    let data = [("cpu_pct", 42.5), ("mem_gib", 3.125)];
    print!("{}", build_report(&data));
}
=== Metrics ===
  cpu_pct   :  42.50
  mem_gib   :   3.12