Display vs Debug in Rust ({} vs {:?})
The difference between Display and Debug in Rust, when to derive Debug, how to implement Display for your own types, and the E0277 errors.
Rust has two main ways to turn a value into text, chosen by the placeholder:
Display |
Debug |
|
|---|---|---|
| Placeholder | {} |
{:?} (or {:#?} pretty) |
| Audience | end users | programmers |
| How you get it | implement by hand | #[derive(Debug)] |
| Strings print as | hello |
"hello" (quoted, escaped) |
| Also gives | .to_string() |
dbg!, assert_eq! messages |
| Implemented by std for | numbers, char, str, String, errors |
almost everything, including Vec, Option, HashMap |
Debug: derive it on nearly everything
#[derive(Debug)] writes an implementation that prints the type name and every field. Derive it on all your structs and enums: tests, dbg!, unwrap failures and logs depend on it, and it costs nothing if unused. {:#?} prints one field per line, which helps with nested data.
Display: you decide the format
Display has no derive, because only you know how a Money or Ticket should look to a user. Implement fmt and write into the formatter with write!:
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 price = Money { cents: 1999 };
println!("{price}");
let label = price.to_string();
println!("{} chars", label.len());
}
$19.99
6 chars
Implementing Display gives you .to_string() for free (through a blanket impl of ToString), so don't implement ToString directly.
Errors need both
The std::error::Error trait requires Debug + Display. Display is the message shown to users ("config file not found"), Debug is for developers. When main returns Err, Rust prints the Debug form.
Respecting width and alignment
write!(f, ...) ignores the caller's format spec, so {:>10} won't pad your type. To support it, build the text and call f.pad(&text), which applies width, alignment and fill.
Common mistakes
Printing a type with {} that only has Debug (or neither):
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 1, y: 2 };
println!("{}", p);
}
error[E0277]: `Point` doesn't implement `std::fmt::Display`
Use {:?} or implement Display. Without #[derive(Debug)], {:?} fails too: "Point doesn't implement Debug".
VecandOptionhave noDisplay. Print them with{:?}, or join the items yourself:names.join(", ").- Using
Debugoutput for users. It's not stable across versions and shows quotes and type names. WriteDisplayfor anything user-facing. - Calling
to_string()insidefmt. It allocates; write directly withwrite!(f, ...).
Example
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq)]
enum Status {
Open,
Closed,
}
#[derive(Debug)]
struct Ticket {
id: u32,
title: String,
status: Status,
tags: Vec<&'static str>,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let text = match self {
Status::Open => "open",
Status::Closed => "closed",
};
f.pad(text) // respects width and alignment like {:>8}
}
}
impl fmt::Display for Ticket {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "#{} {} [{}]", self.id, self.title, self.status)
}
}
fn main() {
let t = Ticket { id: 42, title: String::from("Fix login"), status: Status::Open, tags: vec!["auth", "urgent"] };
println!("{t}");
println!("{t:?}");
println!("{t:#?}");
println!("[{:>8}] [{:<8}]", Status::Closed, t.status);
println!("{:?} vs {}", "quoted", "plain");
println!("tags: {}", t.tags.join(", "));
let line: String = t.to_string();
println!("{} chars", line.len());
}
Output:
#42 Fix login [open]
Ticket { id: 42, title: "Fix login", status: Open, tags: ["auth", "urgent"] }
Ticket {
id: 42,
title: "Fix login",
status: Open,
tags: [
"auth",
"urgent",
],
}
[ closed] [open ]
"quoted" vs plain
tags: auth, urgent
20 chars