Debug
The std::fmt::Debug trait formats a value for programmer-facing diagnostic output using the {:?} (compact) and {:#?} (pretty-printed) format specifiers.
Trait Definition
pub trait Debug {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
}
Every public type in a Rust library should implement Debug, either via #[derive(Debug)] or a manual implementation, so callers can inspect values with println!("{val:?}"), dbg!(val), and assert_eq!(a, b).
Deriving Debug
Applying #[derive(Debug)] generates an implementation that prints the type or variant name along with all fields, provided every field implements Debug:
#[derive(Debug)]
struct ServerConfig {
host: String,
port: u16,
tls_enabled: bool,
}
fn main() {
let config = ServerConfig {
host: String::from("127.0.0.1"),
port: 8080,
tls_enabled: true,
};
println!("Compact: {config:?}");
println!("Pretty:\n{config:#?}");
}
Compact: ServerConfig { host: "127.0.0.1", port: 8080, tls_enabled: true }
Pretty:
ServerConfig {
host: "127.0.0.1",
port: 8080,
tls_enabled: true,
}
Format Specifiers for Debug
| Specifier | Behavior | Example Output for [10, 255] |
|---|---|---|
{:?} |
Single-line compact debug representation | [10, 255] |
{:#?} |
Multi-line indented debug representation | [\n 10,\n 255,\n] |
{:x?} |
Formats integers inside the debug output as lowercase hexadecimal | [a, ff] |
{:X?} |
Formats integers inside the debug output as uppercase hexadecimal | [A, FF] |
Manual Implementation with Formatter Builders
When a struct contains sensitive fields (such as passwords or API tokens) or needs custom formatting, implement std::fmt::Debug manually using the builder methods on std::fmt::Formatter:
| Formatter Method | Purpose |
|---|---|
f.debug_struct("Name") |
Formats a named struct with .field("name", &val) and .finish() (or .finish_non_exhaustive()) |
f.debug_tuple("Name") |
Formats a tuple struct or variant with .field(&val) and .finish() |
f.debug_list() |
Formats a sequence with .entries(iter) and .finish() |
f.debug_set() |
Formats a set of items with .entries(iter) and .finish() |
f.debug_map() |
Formats key-value pairs with .entries(iter) and .finish() |
use std::fmt;
struct DatabaseCredentials {
username: String,
password: String,
}
impl fmt::Debug for DatabaseCredentials {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DatabaseCredentials")
.field("username", &self.username)
.field("password", &"<redacted>")
.finish()
}
}
fn main() {
let creds = DatabaseCredentials {
username: String::from("admin"),
password: String::from("hunter2"),
};
println!("{creds:?}");
}
DatabaseCredentials { username: "admin", password: "<redacted>" }
Using Formatter builders automatically respects both {:?} and {:#?} formatting flags without writing separate indentation logic.