RustArena

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".

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

Practice it