10.7Deriving Debug and printing structs
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.