Nugget 2

Home

Making Your Types Printable

You write println!("{}", my_struct) and the compiler shouts at you. Meet Debug and Display — the two traits that let your types be printed, formatted, and logged.

The Problem

struct Point { x: i32, y: i32 }

fn main() {
    let p = Point { x: 3, y: 7 };
    println!("{}", p);   // ❌ doesn't implement `Display`
    println!("{:?}", p); // ❌ doesn't implement `Debug`
}

Rust won't let you print a struct until you tell it how. This is your first encounter with traits — Debug and Display are standard library traits that control formatting.

Quick Fix: Debug

The fastest way to make a type printable is #[derive(Debug)]. Add it above your struct and use {:?}:

#[derive(Debug)]
struct Point { x: i32, y: i32 }

fn main() {
    let p = Point { x: 3, y: 7 };
    println!("{:?}", p);   // ✅ Point { x: 3, y: 7 }
    println!("{:#?}", p);  // ✅ Pretty-printed:
                           //    Point {
                           //        x: 3,
                           //        y: 7,
                           //    }
}

💡 {:#?} vs {:?}

{:#?} pretty-prints with indentation — great for complex nested structures. {:?} is compact, single-line output.

Custom Output: Display

Debug is auto-generated and often ugly. When you want {} to show something human-readable, implement Display yourself:

use std::fmt;

struct Point { x: i32, y: i32 }

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    let p = Point { x: 3, y: 7 };
    println!("{}", p);   // ✅ (3, 7)
    // {:?} still doesn't work — no Debug
}

⚠️ Display cannot be derived

Rust has no way to know what "human-readable" means for your type — you must write Display by hand. Debug is the only printable trait you can auto-derive.

The Golden Combo

Most Rust code derives Debug for developer tooling and implements Display for users:

use std::fmt;

#[derive(Debug)]                   // auto-generates {:?}
struct Point { x: i32, y: i32 }

impl fmt::Display for Point {      // hand-written for {}
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    let p = Point { x: 3, y: 7 };
    println!("{}", p);    // ✅ (3, 7)              — human friendly
    println!("{:?}", p);  // ✅ Point { x: 3, y: 7 } — dev friendly
    dbg!(&p);             // ✅ dbg!() needs Debug too
}

Bonus: Free .to_string()

Any type that implements Display automatically gets a .to_string() method via a blanket implementation in the standard library:

let p = Point { x: 3, y: 7 };
let s: String = p.to_string();  // ✅ works because of Display
assert_eq!(s, "(3, 7)");

You didn't write to_string() — Rust provided it because Display is implemented. That's the power of traits.

Key Takeaways