Essential Standard Traits

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.