Implementing Debug
🎯 In Python, every object has __repr__ for developer-friendly output:
class Color:
def __init__(self, r, g, b):
self.r, self.g, self.b = r, g, b
def __repr__(self):
return f"Color(r={self.r}, g={self.g}, b={self.b})"
print(repr(Color(255, 0, 0))) # Color(r=255, g=0, b=0)
If you don't implement __repr__, Python gives you something unhelpful like <Color object at 0x...>. So most Python developers learn early to define __repr__ on every class.
Rust's equivalent is the Debug trait. It's what powers {:?} formatting — the format specifier you'll use constantly during development:
println!("{:?}", my_value); // Debug output
println!("{:#?}", my_value); // Pretty-printed Debug output (indented)
Deriving Debug
Here's where Rust differs from Python: you don't have to write any formatting code. The derive macro generates Debug automatically:
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
let origin = Point { x: 0.0, y: 0.0 };
println!("{:?}", origin); // Point { x: 0.0, y: 0.0 }
One line — #[derive(Debug)] — and the compiler writes the __repr__ equivalent for you. The derived output includes the struct name and all fields. Python's dataclasses.dataclass does something similar with auto-generated __repr__, but Rust's derive works on any struct.
The requirement: all fields must themselves implement Debug. Standard types (u8, i32, String, char, Vec<T> where T: Debug) all do.
Manual Debug implementation
Sometimes the derived format isn't what you want. A Score struct might derive as Score { player: "Alice", points: 42 } but you'd prefer Alice: 42 pts.
For custom formatting, you implement fmt::Debug manually:
use std::fmt;
impl fmt::Debug for Score {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {} pts", self.player, self.points)
}
}
The signature: fmt takes &self and a Formatter (the output buffer), returns fmt::Result. The write! macro writes formatted text into the formatter — it works like format! but writes to a destination instead of creating a new String.
This is analogous to Python's:
def __repr__(self):
return f"{self.player}: {self.points} pts"
The difference: Rust's write! writes directly to the formatter buffer instead of allocating a new string. This matters when you're formatting nested structures — each level writes into the same buffer instead of creating intermediate strings.
When to derive vs. implement manually
- Derive when the default
StructName { field: value, ... }format is fine (most of the time) - Implement manually when you want a specific format, want to hide fields, or need to transform values in the output
Your Task
- Add
#[derive(Debug)]to theColorstruct so it can be formatted with{:?} - Implement
DebugforTemperaturemanually — format it as"Temperature(VALUE UNIT)"(e.g.,"Temperature(25 C)"or"Temperature(77 F)")
Example
let red = Color { r: 255, g: 0, b: 0 };
println!("{:?}", red); // Color { r: 255, g: 0, b: 0 }
let temp = Temperature { value: 25, unit: 'C' };
println!("{:?}", temp); // Temperature(25 C)
let cold = Temperature { value: -10, unit: 'C' };
println!("{:?}", cold); // Temperature(-10 C)
Dive deeper: In our Rust Developer Cohort,
#[derive(Debug)]on yourTokenandJsonValuetypes lets you inspect the parser's output with{:?}— essential for development and testing.
Further Reading
- The Rust Book — Printing with Debug — deriving Debug on structs
- std::fmt::Debug — the Debug trait documentation
- std::fmt module — formatting traits overview (Debug, Display, and more)
Topics