We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Shape Your Data: Structs, Enums, Pattern Matching step 18 of 26
#[derive(Debug)] and the three shapes of debug output
Render one of three structs with either {:?} or {:#?}, and return the
result byte for byte.
pub fn render(kind: String, a: i64, b: String) -> String
kind |
output |
|---|---|
unit |
Unit |
unit# |
Unit |
tuple |
Tup(1, "x") |
tuple# |
three lines: Tup(, indented fields, ) |
named |
Named { count: 1, label: "x" } |
named# |
multi-line, one field per line |
| anything else |
? |
Tup is Tup(i64, String) built from (a, b); Named is
Named { count: a, label: b }. The tests pin the exact bytes, so get the
spacing right — or rather, let the derive get it right for you.
{:?} is a trait, not magic
println!("{}", x) uses Display. println!("{:?}", x) uses Debug. Both
are ordinary traits, and a type has them only if someone implemented them.
The first hard error most beginners hit is:
error[E0277]: `Named` doesn't implement `Debug`
note: add `#[derive(Debug)]` to `Named` or manually `impl Debug for Named`
The starter ships it. The fix is one attribute, and — this is the part worth
internalising — #[derive(Debug)] requires every field to be Debug
too. Derive generates impl Debug for Named whose body formats each field,
so a single non-Debug field poisons the whole struct. In practice almost
everything in std is Debug, so the fix is usually to add the attribute
further down your own type tree.
Display is deliberately not derivable
There is no #[derive(Display)] and there never will be. Debug can be
generated because there is one obvious answer: print the type’s name and its
fields. Display is the human-facing rendering, and there is no obvious
answer — should a Duration show as 3s, 00:00:03 or 3000ms? std makes
you decide, by writing impl fmt::Display by hand.
Rule of thumb: Debug is for programmers, Display is for users. If a
string is going in a log line or an error message you are debugging, {:?}
is right and is not laziness.
The three shapes, and {:#?}
Derived Debug mirrors the way you declared the type:
struct Unit; -> Unit
struct Tup(i64, String); -> Tup(1, "x")
struct Named { count, label } -> Named { count: 1, label: "x" }
The # flag turns on alternate formatting — the pretty, one-field-per-line
form with four-space indentation and a trailing comma on every field. It
nests, which is what makes it the right tool for a deeply structured value.
A unit struct has nothing to expand, so {:?} and {:#?} agree on it.
Note also that Debug for String prints the quotes and the escapes:
a string containing a newline shows as "a\nb", not as two lines. That is
the point — Debug is unambiguous where Display is readable.
One caveat about pinning Debug output
Pinning exact Debug bytes is fine here because these are your types and
you control the derive. Do not do it against std types. The standard
library explicitly does not guarantee the stability of Debug output:
the representation of HashMap, Duration, Path and friends can change
between releases without it being a breaking change. Asserting on them makes
a test that fails on a toolchain upgrade for no reason.
Two allow-by-default lints in the area
missing_fields_in_debug catches a hand-written Debug that quietly
skips a field — often how a secret ends up invisible in one log line and
visible in another. empty_structs_with_brackets prefers struct Unit;
over struct Unit {}.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.