Formatting Output
Rust's formatting system (std::fmt, re-exported macros format!, print!, println!, eprint!, eprintln!, write!, writeln!, format_args!) compiles format strings at compile time into type-checked calls to std::fmt::Display, std::fmt::Debug, and specialized numeric/pointer formatting traits.
Formatting Specifier Mini-Language
Every placeholder inside a format string follows the grammar { [argument] [ ':' [[fill]align] [sign] ['#'] ['0'] [width] ['.' precision] [type] ] }:
Format Specifier Anatomy: `{value:+010.2?}`
^^^^^ ^^ ^ ^ ^
| || | | +-- Type (`?` Debug, `x` hex, `b` binary, `e` exp)
| || | +---- Precision (`.2` decimal places / max str chars)
| || +------- Width (`10` minimum total characters)
| |+--------- Zero-pad (`0`) or `[fill][< | ^ | >]` alignment
| +---------- Sign (`+` always show `+` or `-`) / `#` alternate
+---------------- Positional, named, or captured variable identifier
| Specifier Example | Input Value | Formatted Output | Meaning |
|---|---|---|---|
{x:>8} |
42 |
" 42" |
Right-aligned in a field of width 8 |
{x:<8} |
"rust" |
"rust " |
Left-aligned in a field of width 8 |
{x:-^10} |
"Title" |
"--Title---" |
Center-aligned with '-' fill character in width 10 |
{x:+06} |
42 |
"+00042" |
Leading sign + and zero-padded to width 6 |
{x:#06x} |
255 |
"0x00ff" |
Alternate prefix 0x, zero-padded to width 6, lowercase hex |
{x:#010b} |
5 |
"0b00000101" |
Alternate prefix 0b, zero-padded to width 10, binary |
{x:.3} |
3.14159 |
"3.142" |
Floating-point rounded to 3 decimal places |
{s:.4} |
"abcdefg" |
"abcd" |
String truncated to at most 4 characters |
{x:?} / {x:#?} |
vec![1, 2] |
"[1, 2]" |
Compact Debug vs. pretty multi-line Debug |
Dynamic Width and Precision (width$ and prec$)
Both the field width and precision can be supplied dynamically at runtime from another argument or variable using the $ suffix ({val:>width$.prec$}):
use std::io::{self, Write};
fn main() -> io::Result<()> {
let mut out = io::stdout().lock();
writeln!(out, "{:<12} | {:>10} | {:>8}", "Metric", "Hex", "Value")?;
writeln!(out, "{:-<12}-+-{:->10}-+-{:->8}", "", "", "")?;
let rows = [("requests", 255_u32, 99.946_f64), ("errors", 12_u32, 0.054_f64)];
let dec_places = 2;
for (name, count, rate) in rows {
writeln!(
out,
"{name:<12} | {count:#010x} | {rate:>7.prec$}%",
prec = dec_places
)?;
}
Ok(())
}
Metric | Hex | Value
-------------+------------+---------
requests | 0x000000ff | 99.95%
errors | 0x0000000c | 0.05%
Zero-Allocation Formatting with write! and format_args!
Calling out.write_all(format!("id={id}\n").as_bytes()) allocates a temporary String on the heap just to copy its bytes into out and immediately free the String. Using writeln!(out, "id={id}") (which passes a stack-allocated std::fmt::Arguments<'_> created by format_args! directly into Write::write_fmt) formats values into out with zero heap allocations!