10.7Deriving Debug and printing structs

Last updated June 13, 2026

You can build a struct and give it methods. Now try the most natural thing in the world, printing one:

struct Point {
    x: f64,
    y: f64,
}

fn main() {
    let p = Point { x: 3.0, y: 4.0 };
    println!("{}", p);
}
error[E0277]: `Point` doesn't implement `std::fmt::Display`
 --> src/main.rs:8:20
  |
8 |     println!("{}", p);
  |                    ^ `Point` cannot be formatted with the default formatter
  |
  = help: the trait `std::fmt::Display` is not implemented for `Point`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

The compiler isn't being difficult; it genuinely doesn't know how. Lesson 5.5 hinted at this: {} needs the type to say how it displays, and a type you just invented hasn't said. Should a Point print as (3, 4)? 3, 4? x=3 y=4? The compiler can't choose for you, so {} (the Display formatter, meant for human-facing output) is unavailable until you write that choice yourself, which is a chapter 16 topic.

But notice the note: the compiler is already pointing at the answer.

#[derive(Debug)] and

There's a second formatter, {:?}, the debug formatter from lesson 5.5, meant for programmers rather than end users. Its job is to show a value's structure so you can inspect it, and the compiler can write that one for you automatically. You ask with a derive attribute on the struct:

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

fn main() {
    let p = Point { x: 3.0, y: 4.0 };
    println!("{:?}", p);
}
Point { x: 3.0, y: 4.0 }

One line, #[derive(Debug)], and Point is printable with {:?}. The output shows the type name and every field with its value, which is exactly what you want when debugging: a faithful, unambiguous dump of what's actually in the value. This is {:?} finally paying off the promise lesson 4.11 made, now for your own types.

An attribute is the #[...] syntax: an instruction to the compiler attached to the item below it. You've seen one before in passing (#[test] was teased in lesson 3.6). derive is the attribute that asks the compiler to generate code.

Pretty-printing with

For a struct with more than a couple of fields, the one-line form gets hard to read. Add the # flag, giving {:#?}, for the pretty-printed, multi-line version:

#[derive(Debug)]
struct Config {
    width: u32,
    height: u32,
    title: String,
}

fn main() {
    let c = Config {
        width: 1920,
        height: 1080,
        title: String::from("home"),
    };
    println!("{:#?}", c);
}
Config {
    width: 1920,
    height: 1080,
    title: "home",
}

Same information, one field per line, indented. Reach for {:#?} when you're eyeballing a big value, {:?} when you want it compact. Both come from the single #[derive(Debug)].

Tip

The dbg! macro from lesson 3.4 uses this same Debug machinery. Once a struct derives Debug, you can wrap any expression involving it in dbg!(...) and get the file, line, and a pretty-printed value on stderr. Deriving Debug on basically every struct you write is standard practice precisely because it unlocks both {:?} and dbg!.

What derive actually does

#[derive(Debug)] is the compiler writing boilerplate so you don't have to. "Print this struct by showing its name and fields" is mechanical: given the definition, there's an obvious correct implementation, and writing it by hand for every struct would be tedious and error-prone. So the compiler offers to write it, and derive is how you accept.

Debug is one of several behaviors the compiler can derive. You met Default last lesson (#[derive(Default)]); others you'll meet soon include Clone (the lesson 8.7 deep-copy, derivable so you don't hand-write it), Copy, and PartialEq (which makes == work on your type). You can ask for several at once:

#[derive(Debug, Clone, Default)]
struct Point {
    x: f64,
    y: f64,
}

Each name in the list is a distinct piece of behavior, called a trait, that the compiler now implements for your type. That word is chapter 16's whole story, and derive will make complete sense once you've seen what a trait is and how you'd implement one by hand. For this chapter, the working understanding is enough: derive is a request for the compiler to write standard, obvious code on your behalf, and Debug is the one you'll reach for constantly.

Key insight

{} is for your users; {:?} is for you. Display ({}) is a deliberate, human-facing presentation you must write yourself, because only you know how a Point should read in a sentence. Debug ({:?}) is a faithful structural dump the compiler can generate, because there's an obvious right answer. That's why one is derivable and the other isn't.

Quiz time

Question #1

This program is refused. What's the one-line fix?

struct Temperature {
    celsius: f64,
}

fn main() {
    let t = Temperature { celsius: 21.5 };
    println!("{:?}", t);
}
Show solution

Temperature doesn't implement Debug, so {:?} is unavailable (E0277). Add #[derive(Debug)] above the struct. Then it prints Temperature { celsius: 21.5 }.

Question #2

What's the difference between {} and {:?}, and why can the compiler derive one but not the other?

Show solution

{} is Display, human-facing output you must define yourself because the language can't guess how your type should read in prose. {:?} is Debug, a programmer-facing structural dump the compiler can generate automatically because "show the type name and fields" has an obvious correct form. So Debug is derivable; Display is not.

Question #3

What does the # add in {:#?} versus {:?}?

Show solution

The # selects the pretty-printed form: each field on its own indented line, instead of the compact single-line {:?}. Same data and same derive, just laid out for readability when the value is large.

Deriving behavior is a hint of chapter 16, dropped here because you'll want Debug immediately. Next lesson returns to ownership: structs own their fields, which has consequences for moves, and storing a reference in a struct opens a door we'll keep shut until chapter 17.