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
- println!, format!, and eprintln! and when to use each
- Display {} vs Debug {:?} vs pretty Debug {:#?}
- Positional {0}, named, and inline-capture arguments
- Width, precision, fill, and alignment specifiers
- Printing numbers in binary {:b} and hex {:x}
- Escaping literal braces with {{ and }}
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]
// ffMini-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)
- ❌ "X doesn't implement std::fmt::Display" — You used {} on a type without Display. ✅ Use {:?} and add #[derive(Debug)].
- ❌ "invalid reference to positional argument" — A {0} index has no matching argument. ✅ Make sure indices line up with the arguments you passed.
- ❌ "argument never used" — You passed more arguments than placeholders. ✅ Add a placeholder or remove the extra argument.
- ❌ A literal { breaks the format — It's read as a placeholder. ✅ Double it: write {{ and }} for literal braces.
Pro Tips
- 💡 Prefer inline capture ({name}) for short, readable format strings.
- 💡 Use {:#?} when debugging nested data — the indented multi-line form is far easier to read.
- 💡 Build tables with fixed widths per column and alignment flags; everything lines up with zero manual spacing.
📋 Quick Reference — Format Specs
| Spec | Effect |
|---|---|
| {} / {:?} | 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!
- ✅ println! prints, format! returns a String, eprintln! writes to stderr
- ✅ {} = Display, {:?} = Debug, {:#?} = pretty Debug
- ✅ Arguments can be positional, named, or inline-captured
- ✅ Specs control width, precision, fill, alignment, and radix; {{}} escapes braces
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
- Previous: Tuples & Arrays
- Next: Functions — Define functions, pass parameters, and return values with expressions
- Quick reference: Rust cheat sheet
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.