Standard Input and Output

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!