Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 18 of 26

Easy Primitives

#[derive(Debug)] and the three shapes of debug output

Add the missing Debug implementation to the starter, then use its render function to format unit, tuple and named-field structs.

pub fn render(kind: String, a: i64, b: String) -> String

Keep the three struct names and their fields. Tup stores (a, b); Named stores count: a and label: b. Match kind exactly, including case:

kind value formatting
unit Unit {:?}
unit# Unit {:#?}
tuple Tup(a, b) {:?}
tuple# Tup(a, b) {:#?}
named Named { count: a, label: b } {:?}
named# Named { count: a, label: b } {:#?}
anything else return "?" no formatting

Return the formatted string without adding a final newline. Unit and unknown kinds ignore a and b. The number can be any i64; the string can be empty or contain Unicode, quotes, backslashes and control characters.

Enable Debug for the named struct

{:?} requests the std::fmt::Debug trait. The starter already derives it for Unit and Tup, but not for Named, so the formatting calls fail with E0277. Add #[derive(Debug)] above Named.

Deriving Debug formats the fields using their own Debug implementations. Here both field types, i64 and String, implement the trait. A struct with a field that cannot be debug-formatted needs a different implementation or a change to that field's type.

Read compact and pretty output

With a = 1 and b = "x", compact formatting produces:

Unit
Tup(1, "x")
Named { count: 1, label: "x" }

These are three separate results. The alternate flag in {:#?} asks the derived implementation for pretty output. For tuple#, the result has four lines, with four spaces before each field:

Tup(
    1,
    "x",
)

For named#, it also has four lines:

Named {
    count: 1,
    label: "x",
}

Each field has a trailing comma. The unit struct still produces Unit under either format because it has no fields to expand.

String contents and formatting traits

String debug output includes surrounding quotes and escapes characters such as newlines. This complete example shows the difference from {}, which requests Display:

fn main() {
    let text = "a\nb";
    assert_eq!(format!("{text:?}"), "\"a\\nb\"");
    assert_eq!(format!("value: {text}"), "value: a\nb");
}

Debug is intended for inspecting values; Display lets a type choose a user-facing representation. Rust has no built-in Display derive, though procedural macros can provide one. Implementing one trait does not implement the other. Custom Debug implementations may omit or transform fields, so debug output is not a general serialization format or a guarantee that every detail is visible. Derived output also does not automatically redact secrets.

Exact-output tests have a version limit

The fixtures compare exact strings. The examples and fixture bytes have been checked with Rust 1.98.1. Use derived formatting for this exercise rather than manually recreating spaces and escaping.

Rust's Debug documentation does not promise stable output across compiler releases, including when you derive the trait for your own structs. These tests illustrate the current format; they do not establish a permanent format contract. A toolchain upgrade needs the fixtures checked again. For persistent data or a protocol, choose an explicitly specified format instead.