RustArena

Step 7 of 7

Display versus Debug

{} and {:?} call two different formatting abilities (traits):

fn main() {
    let name = "Ferris \"the crab\"";
    println!("{}", name);
    println!("{:?}", name);
    println!("{:?}", "a\tb");
    println!("{} vs {:?}", 'x', 'x');
    println!("{:?} {:?}", Some("ok"), vec!["a", "b"]);
}
Ferris "the crab"
"Ferris \"the crab\""
"a\tb"
x vs 'x'
Some("ok") ["a", "b"]

Debug output is what you want in logs and test failures: with {:?} you can see a stray tab or trailing space that {} would hide.

Collections and Option only have Debug. There's no single "right" way to show a list to a user, so Rust leaves it to you:

fn main() {
    println!("{}", vec![1, 2]);
}
error[E0277]: `Vec<{integer}>` doesn't implement `std::fmt::Display`

Display for your own types

#[derive(Debug)] writes Debug for you (module 12). Display you write yourself, because only you know how your type should look to users. The shape is always the same:

use std::fmt;

struct Money {
    cents: i64,
}

impl fmt::Display for Money {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let sign = if self.cents < 0 { "-" } else { "" };
        let abs = self.cents.abs();
        write!(f, "{}${}.{:02}", sign, abs / 100, abs % 100)
    }
}

fn main() {
    let price = Money { cents: 1999 };
    let refund = Money { cents: -250 };
    println!("{price} and {refund}");
    let text = price.to_string();
    println!("{} chars: {text}", text.len());
}
$19.99 and -$2.50
6 chars: $19.99

Common mistake: width on your own type

A width like {:>10} is handled by each type's fmt, and a fmt that just calls write! ignores it:

use std::fmt;

struct Money {
    cents: i64,
}

impl fmt::Display for Money {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "${}.{:02}", self.cents / 100, self.cents % 100)
    }
}

fn main() {
    let m = Money { cents: 1999 };
    println!("[{:>10}]", m);
    println!("[{:>10}]", m.to_string());
}
[$19.99]
[    $19.99]

Format it into a String first (as above), or make your fmt end with f.pad(&text), which applies the caller's width and alignment.

Your task

Implement Display for Elapsed (a number of seconds) the way a CLI shows durations: 45 seconds is 45s, 125 is 2m 05s, 3725 is 1h 02m 05s. Minutes and seconds after the first unit are padded to two digits; hours appear only if there's at least one, minutes only if there are hours or minutes.

Challenge 2

Implement Display for Level (INFO, WARN, ERROR) and for LogLine, which displays as [LEVEL] message, for example [WARN] disk 91% full. Inside LogLine's fmt you can use {} on self.level once Level has Display.

Challenge 3

Implement Display for Grid so it prints as a table: every number right-aligned in a field 4 characters wide, rows separated by newlines (no newline after the last row). For [[1, 20], [300, -4]]:

   1  20
 300  -4

Build the whole text in a String with format!("{:>4}", n) pieces, then write! it once.

Previous: Formatting: width, alignment, precision