Step 7 of 7
Display versus Debug
{} and {:?} call two different formatting abilities (traits):
- Display (
{}) is the text for users: clean, no quotes, decided by the type's author. - Debug (
{:?}) is the text for programmers: unambiguous, showing quotes, escapes and structure.{:#?}is the pretty, multi-line version.
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
impl fmt::Display for Moneysays "here's how Money does Display". It has one method,fmt, which you copy in this exact shape. (Formatter<'_>contains a lifetime placeholder; module 24 explains it, and you can copy it as is.)write!(f, ...)works likeformat!, but writes into the formatterf. Its result, afmt::Result, is whatfmtreturns.- Once a type has Display,
{}works for it everywhere (println!,format!,panic!), and you get.to_string()for free. - Need several
write!calls? Each returns afmt::Result, and real code ends them with?to stop on an error (module 19). Until then, build the text in aStringandwrite!it once.
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.