Why this matters
Output is how your program talks to people and to other programs, and it is also your main debugging tool for a while: when something behaves oddly, you print values and look. Rust's formatting macros are compact and checked at compile time, so it is worth learning the handful of rules properly once. Every exercise in this course is checked by comparing your output with the expected text, so precise control over spaces, alignment and decimals matters here more than anywhere.
The family of macros
All of these take a format string followed by values:
| macro | writes to | newline at end |
|---|---|---|
println! | standard output (stdout) | yes |
print! | standard output | no |
eprintln! | standard error (stderr) | yes |
eprint! | standard error | no |
format! | nowhere: returns the text as a String | no |
Every process starts with three open streams: standard input (stdin), standard output and standard error. By default both output streams go to the terminal, so they look the same. They can be redirected separately, though: prog > results.txt sends stdout to the file while stderr still shows on screen. The convention is that results go to stdout and diagnostics to stderr, so a program's real output can be piped into another program without progress messages mixed in.
fn main() {
print!("no newline, ");
print!("still same line\n");
eprintln!("this goes to stderr");
let s = format!("{}-{}", "a", 1);
println!("{s}");
}
no newline, still same line
this goes to stderr
a-1
\n inside a string literal is the newline character; println! simply adds one for you.
Placeholders
A {} in the format string is replaced by the next value:
fn main() {
let name = "Ada";
let year = 1843;
println!("{} wrote the first program in {}", name, year);
println!("{name} wrote the first program in {year}");
println!("{0} and {1}, then {0} again", "A", "B");
println!("{{literal braces}}");
}
Ada wrote the first program in 1843
Ada wrote the first program in 1843
A and B, then A again
{literal braces}
Three ways to choose the value:
- Positional, in order: each
{}takes the next argument. - Inline names:
{name}uses a variable callednamethat is in scope. This is usually the most readable form. (It only accepts a plain variable name, not an expression like{a + b}.) - Indexed:
{0},{1}refer to arguments by position, so one can be used twice.
To print a literal brace, double it: {{ and }}.
The let name = "Ada"; lines create variables, names bound to values. Module 2 covers them properly; for now read let x = value; as "from here on, x means value".
Display and Debug
Inside the braces, after a colon, you can say how to format. The most important choice is between two formats:
{}is Display: the user-facing form. A string prints as its text.{:?}is Debug: the programmer-facing form. Strings get quotes and escapes, and compound values show their structure.
fn main() {
println!("{}", "tab\there");
println!("{:?}", "tab\there");
println!("{:?}", (1, "two", 3.0));
}
tab here
"tab\there"
(1, "two", 3.0)
The Debug form shows that the gap is a tab character, which the Display form hides. The last line prints a tuple (module 2), something that has no Display form at all: println!("{}", (1, 2)) does not compile, because there is no single obvious way to show a tuple to a user. Debug, on the other hand, exists for almost every standard type. {:#?} is "pretty" Debug, spread over several lines, handy for big structures.
Width, alignment and fill
A number after the colon is a minimum width. Values shorter than the width are padded:
fn main() {
let name = "Ada";
println!("[{:>8}]", name);
println!("[{:<8}]", name);
println!("[{:^8}]", name);
println!("[{:*^9}]", name);
println!("[{:5}]", 42);
println!("[{:05}]", 42);
}
[ Ada]
[Ada ]
[ Ada ]
[***Ada***]
[ 42]
[00042]
<left,>right,^center.- A character before the alignment sign is the fill:
{:*^9}centers in 9 columns, padding with*. - Without an alignment sign, text is left-aligned and numbers right-aligned.
0before the width pads numbers with zeros.- Width is a minimum: a longer value is printed in full, never cut.
The width can come from a variable: {:>w$} uses the variable w, {:>1$} uses argument 1.
Precision for floating-point numbers
For floats, .N sets the number of digits after the decimal point, with rounding:
fn main() {
let pi = 3.14159265;
println!("{:.2}", pi);
println!("[{:8.3}]", pi);
println!("{}", 1.0);
println!("{:?}", 1.0);
println!("{}", 0.1 + 0.2);
}
3.14
[ 3.142]
1
1.0
0.30000000000000004
Width and precision combine: {:8.3} is 8 columns wide with 3 decimals. Note that Display prints 1.0 as 1, while Debug keeps the .0. The last line is not a Rust bug: 0.1 cannot be stored exactly in binary, and the next module explains why. When you print money or measurements, always choose a precision.
Number bases
fn main() {
println!("{:x} {:X} {:o} {:b} {:#x} {:#b}", 255, 255, 8, 5, 255, 5);
println!("{:e}", 1234.5);
}
ff FF 10 101 0xff 0b101
1.2345e3
x/X hexadecimal, o octal, b binary, e scientific notation; # adds the 0x, 0o or 0b prefix. These will be useful when we look at how numbers are stored in bits.
The full grammar, in order, is {name_or_index:fill align sign # 0 width .precision type}, every part optional. You will rarely need more than two parts at once.
Common mistakes
Wrong number of arguments. Checked at compile time:
error: 2 positional arguments in format string, but there is 1 argument
The reverse, an argument with no placeholder, is also an error: argument never used.
Printing something without Display.
fn main() {
println!("{}", (1, 2));
}
error[E0277]: `({integer}, {integer})` doesn't implement `std::fmt::Display`
--> main.rs:2:20
|
2 | println!("{}", (1, 2));
| ^^^^^^ `({integer}, {integer})` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `({integer}, {integer})`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
The note tells you the fix: use {:?}.
Expressions in inline names. println!("{x + 1}") is an error; write println!("{}", x + 1).
Forgetting that print! does not flush a newline. print!("a"); print!("b"); prints ab on one line. If an exercise expects separate lines, use println!.
Trailing spaces. In exercises, {:<8} at the end of a line produces trailing spaces you cannot see. Most checks ignore trailing spaces, but not all; pad only where the format needs it.
Recap
println!/print!write to stdout,eprintln!/eprint!to stderr,format!returns aString.{}is Display (for users),{:?}is Debug (for programmers),{:#?}pretty Debug.- Choose the value by position, inline name
{x}, or index{0};{{prints a brace. {:>8},{:<8},{:^8}align in a width;{:08}zero-pads;{:.2}sets float precision.{:x},{:o},{:b},{:e}change the base or notation;#adds a prefix.
Going further: the complete specification is in the std::fmt module documentation (doc.rust-lang.org/std/fmt). Try {:+} on a positive number, and {:>10.3e} on a large float, and predict the result before running it.