Getting Started: Programs and the Compiler · about 35 min

Printing and Formatting Output

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:

macrowrites tonewline at end
println!standard output (stdout)yes
print!standard outputno
eprintln!standard error (stderr)yes
eprint!standard errorno
format!nowhere: returns the text as a Stringno

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:

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:

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]

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

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.

Exercises

Exercise 1 · write code

Named placeholders

Using the two variables already defined, print exactly:

Grace Hopper was born in 1906.
1906 is the year; Grace Hopper is the name.

Use inline names such as {name} in the format strings instead of typing the values.

File: main.rs

Starting point

fn main() {
    let name = "Grace Hopper";
    let year = 1906;
}

Example

expected output

Grace Hopper was born in 1906.
1906 is the year; Grace Hopper is the name.
Hint 1

println!("{name} was born in {year}."); prints the first line.

Reference solution
fn main() {
    let name = "Grace Hopper";
    let year = 1906;
    println!("{name} was born in {year}.");
    println!("{year} is the year; {name} is the name.");
}

Exercise 2 · write code

A small aligned table

Print this price table exactly. The item column is 8 characters wide and left-aligned, the price column is 7 characters wide, right-aligned, with exactly 2 digits after the decimal point. Use width and precision in the format specifiers rather than typing the spaces yourself.

item      price
apple      0.50
bread      2.25
coffee    11.00

The header line is item padded to 8, then price right-aligned in 7.

File: main.rs

Starting point

fn main() {
    println!("{:<8}{:>7}", "item", "price");
    // print the three rows: apple 0.5, bread 2.25, coffee 11.0
}

Example

expected output

item      price
apple      0.50
bread      2.25
coffee    11.00
Hint 1

{:<8} left-aligns in 8 columns; {:>7.2} right-aligns in 7 columns with 2 decimals.

Hint 2

Pass the prices as numbers, 0.5, 2.25, 11.0, and let .2 add the zeros.

Reference solution
fn main() {
    println!("{:<8}{:>7}", "item", "price");
    println!("{:<8}{:>7.2}", "apple", 0.5);
    println!("{:<8}{:>7.2}", "bread", 2.25);
    println!("{:<8}{:>7.2}", "coffee", 11.0);
}

Exercise 3 · write code

One number, four bases

Print the number 202 in decimal, hexadecimal (lowercase, with the 0x prefix), octal and binary (with the 0b prefix), exactly like this:

decimal 202
hex 0xca
octal 312
binary 0b11001010

Store 202 in a variable once and format it four ways.

File: main.rs

Starting point

fn main() {
    let n = 202;
}

Example

expected output

decimal 202
hex 0xca
octal 312
binary 0b11001010
Hint 1

x is hex, o octal, b binary; # adds the 0x/0b prefix.

Reference solution
fn main() {
    let n = 202;
    println!("decimal {}", n);
    println!("hex {:#x}", n);
    println!("octal {:o}", n);
    println!("binary {:#b}", n);
}

Exercise 4 · write code

Results to stdout, messages to stderr

Programs should print their results on standard output and progress or error messages on standard error, so that prog > out.txt captures only results.

Write a program that prints working... to stderr, then answer: 42 to stdout, then done to stderr. The checker looks at stdout and stderr separately.

File: main.rs

Starting point

fn main() {
    println!("working...");
    println!("answer: 42");
    println!("done");
}

Example

expected output

answer: 42
Hint 1

eprintln! works exactly like println! but writes to stderr.

Reference solution
fn main() {
    eprintln!("working...");
    println!("answer: 42");
    eprintln!("done");
}

Exercise 5 · quiz

{} or {:?}

What does this print?

fn main() {
    let word = "hi";
    println!("{} {:?}", word, word);
}
Answer
  • hi "hi"

{} uses the Display format, meant for end users: the text itself. {:?} uses the Debug format, meant for programmers: strings appear in quotes with special characters escaped, so you can see exactly what the value is, including stray spaces or newlines. When you are puzzled by a value, print it with {:?}.