String Formatting (format!, println!)

Reviewed & published by Brayan K

Rust's formatting macros — println!, format!, and eprintln! — substitute values into a template using {} placeholders that also control width, precision, alignment, and number base.

Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll learn Display vs Debug formatting, positional and named arguments, the width/precision/fill mini-language, printing in binary and hex, and how to escape braces.

What You'll Learn in This Lesson

1️⃣ The Core Macros & Display vs Debug

println! writes to standard output, eprintln! to standard error, and format! returns a String instead of printing. The placeholder {} uses Display (user-facing); {:?} uses Debug (developer-facing, works for tuples and collections).

fn main() {
    // println! prints to standard output with a trailing newline.
    let name = "Ada";
    let age = 36;
    println!("{} is {} years old", name, age);   // {} = Display formatting

    // format! builds a String instead of printing it.
    let line = format!("{}-{}", name, age);
    println!("built: {}", line);

    // eprintln! prints to standard ERROR (stderr), for diagnostics. It is NOT
    // in the expected output below: that panel is stdout, and this line is not
    // on it — which is the whole point of a separate stream.
    eprintln!("(this went to stderr)");

    // {:?} is DEBUG formatting — great for collections and tuples.
    let pair = (1, "two");
    let nums = vec![10, 20, 30];
    println!("{:?}", pair);
    println!("{:?}", nums);
}

The (this went to stderr) line came from eprintln!. In your terminal both streams interleave, but they're separate channels — redirecting output (cargo run > out.txt) keeps stderr on screen.

2️⃣ Positional, Named & Inline Arguments

Empty {} consume arguments in order, but you can also reference them by index ({0}, {1}) to reuse or reorder, give them names, or — most concise — capture a variable directly inside the braces.

fn main() {
    // Positional arguments: {0}, {1} reuse args by index, in any order.
    println!("{0} {1} {0}", "ab", "cd");

    // Named arguments make long format strings readable.
    println!("{lang} is {kind}", lang = "Rust", kind = "safe");

    // Inline captures: name a variable directly inside the braces.
    let user = "ada";
    let count = 3;
    println!("{user} has {count} items");

    // {:#?} is PRETTY debug — multi-line, indented. Great for nested data.
    let data = vec![(1, "a"), (2, "b")];
    println!("{:#?}", data);
}

3️⃣ Width, Precision, Alignment & Radix

After a colon comes the format spec: an optional fill char and alignment (< left, > right, ^ center), a minimum width, then a precision after a dot. You can also choose a number base with :b, :o, :x, and add # for the prefix.

fn main() {
    // WIDTH: {:8} pads to at least 8 chars (right-aligned for numbers).
    println!("[{:8}]", 42);
    // FILL + ALIGN: {:<} left, {:>} right, {:^} center; a fill char goes first.
    println!("[{:<8}]", "hi");      // left-aligned in 8
    println!("[{:>8}]", "hi");      // right-aligned in 8
    println!("[{:^8}]", "hi");      // centered in 8
    println!("[{:*^8}]", "hi");     // centered, filled with '*'

    // PRECISION for floats: {:.2} keeps 2 decimal places.
    println!("{:.2}", 3.14159);
    // Combine width.precision: total width 10, 3 decimals.
    println!("[{:10.3}]", 2.5);

    // RADIX: binary, octal, hex (with # for the 0b/0o/0x prefix).
    println!("{:b}", 42);
    println!("{:o}", 42);
    println!("{:x}", 255);
    println!("{:#x}", 255);

    // Escape literal braces by doubling them.
    println!("a literal brace: {{}}");
}

These specs make aligned tables trivial: pick a width per column and an alignment, and rows line up perfectly. Note the last line — doubling braces ({{}}) prints a literal {} with no substitution.

Your turn. Fill in the blanks marked ___, then run it.

// 🎯 YOUR TURN — fill in the blanks, then run it.
fn main() {
    let item = "widget";
    let price = 4.5;

    // 1) Print "widget costs $4.50": inline-capture both, 2 decimals on price.
    println!("{item} costs ${price:.___}");

    // 2) Right-align the number 7 in a field of width 5.
    println!("[{:___5}]", 7);

    // 3) Print 255 in lowercase hexadecimal.
    println!("{:___}", 255);
}

// ✅ Expected output:
//    widget costs $4.50
//    [    7]
//    ff

Mini-Challenge: A Tidy Receipt

Use widths, alignment, and precision to print a perfectly columned receipt. Run it with cargo run and check the output.

// 🎯 MINI-CHALLENGE: a tidy receipt.
//
// Given [("Apples", 3, 0.50), ("Bread", 1, 2.25), ("Milk", 2, 1.40)]
// (name, quantity, unit price):
// 1. Print a header row: Item left-aligned width 8, Qty right width 4,
//    Total right width 8.
// 2. For each line compute total = qty * price and print it with the SAME
//    widths, total to 2 decimals.
// 3. Print a TOTAL row summing every line.
//
// ✅ Example output:
//    Item     Qty   Total
//    Apples     3    1.50
//    Bread      1    2.25
//    Milk       2    2.80
//    TOTAL           6.55

fn main() {
    let items = [("Apples", 3, 0.50), ("Bread", 1, 2.25), ("Milk", 2, 1.40)];
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Format Specs

SpecEffect
{} / {:?}Display / Debug
{:#?}Pretty (multi-line) Debug
{:>8} {:^8}Right / center in width 8
{:.2}2 decimal places
{:b} {:x} {:#x}Binary / hex / hex w/ prefix
{{ }}Literal braces

🎉 Lesson Complete!

Practice quiz

Which placeholder uses Display formatting?

  • {:?}
  • {:#?}
  • {}
  • {:b}

Answer: {}. {} uses the Display trait for user-facing output.

Which placeholder uses Debug formatting?

  • {:?}
  • {}
  • {:x}
  • {:.2}

Answer: {:?}. {:?} uses the Debug trait, which works for tuples and collections.

What does format! do compared to println!?

  • Prints to stderr
  • Panics
  • Reads input
  • Returns a String instead of printing

Answer: Returns a String instead of printing. format! builds and returns a String rather than printing.

Where does eprintln! send its output?

  • Standard output
  • Standard error (stderr)
  • A file
  • A String

Answer: Standard error (stderr). eprintln! writes to standard error for diagnostics.

What does {:.2} do to a float?

  • Keeps 2 decimal places
  • Pads to width 2
  • Prints in base 2
  • Rounds to an integer

Answer: Keeps 2 decimal places. Precision .2 keeps two digits after the decimal point.

What does {:>8} do?

  • Left-aligns in width 8
  • Centers in width 8
  • Right-aligns in width 8
  • Prints 8 copies

Answer: Right-aligns in width 8. > means right-align, padded to a minimum width of 8.

What does {:x} print for 255?

  • 255
  • ff
  • 0xff
  • 11111111

Answer: ff. {:x} prints lowercase hexadecimal, so 255 becomes ff.

What does {:#x} print for 255?

  • ff
  • 255
  • #ff
  • 0xff

Answer: 0xff. The # flag adds the 0x prefix, giving 0xff.

How do you print a literal curly brace?

  • \{
  • Double it: {{ and }}
  • {brace}
  • You cannot

Answer: Double it: {{ and }}. Escape literal braces by doubling them: {{ prints { and }} prints }.

What does {:#?} produce?

  • Hex output
  • Display output
  • Pretty multi-line Debug
  • Binary output

Answer: Pretty multi-line Debug. {:#?} is the pretty, indented multi-line Debug form.

Continue this course

Frequently asked questions

What's the difference between {} and {:?}?

The {} placeholder uses the Display trait — a clean, human-facing representation meant for end users, which not every type implements. The {:?} placeholder uses the Debug trait — a developer-facing representation that shows structure, including quotes around strings and brackets around collections. You can usually derive Debug for your own types with #[derive(Debug)], which is why {:?} works out of the box for tuples, Vecs, and most standard types, while {} requires the author to have implemented Display deliberately. Use {} for output users read and {:?} for logging and debugging.

How do I print a literal curly brace?

Because { and } are the placeholder delimiters, you escape a literal brace by doubling it: {{ prints a single { and }} prints a single }. For example, println!("set: {{1, 2, 3}}") prints the text set: {1, 2, 3} with no substitution. This is the formatting equivalent of escaping a quote, and it's the one piece of escaping you'll reach for most when generating code, JSON, or LaTeX-like text from Rust.

What does the format string after the colon mean, like {:>8.2}?

Everything after the colon is a mini format spec. It can include, in order, an optional fill character and alignment (< left, > right, ^ center), a minimum width, and a precision introduced by a dot. So {:>8.2} means: right-align, pad to at least width 8, and show 2 digits after the decimal point. You can also request a radix with b (binary), o (octal), or x/X (hex), and add # for the 0b/0o/0x prefix or for the pretty multi-line Debug form {:#?}. These specs work identically across println!, format!, and write!.

When should I use format! instead of println!?

Use println! (or eprintln! for standard error) when you want to send text to the terminal immediately. Use format! when you want to build a String value to store, return from a function, concatenate, or pass somewhere else — it uses the exact same placeholder syntax but produces a String instead of printing. Under the hood they share the same machinery, so anything you can write in a println! format string works in format! too. There's also write!, which formats into an existing buffer or writer.