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