Implement custom Debug in Rust
Implement std::fmt::Debug manually when #[derive(Debug)] exposes the wrong representation. The usual struct-shaped implementation uses Formatter::debug_struct(), adds only the fields you want, and finishes the builder.
struct Account {
username: &'static str,
api_key: &'static str,
}
impl fmt::Debug for Account {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Account")
.field("username", &self.username)
.field("api_key", &"[redacted]")
.finish()
}
}RustPrint runs this implementation in CI. The test verifies that the visible field is present, the placeholder is present, and the original secret value is absent from the formatted output.
The Debug trait method
Section titled “The Debug trait method”A manual implementation provides one method:
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::ResultThe formatter is the destination and carries formatting options such as the alternate # flag used by {:#?}.
For most structs, use the formatter’s debug builder rather than manually assembling punctuation. debug_struct() produces the normal struct-shaped Debug representation and supports pretty Debug automatically.
Choose which fields appear
Section titled “Choose which fields appear”Unlike #[derive(Debug)], a manual implementation does not have to include every field. You decide which calls to .field() are made and what value each field shows.
That is useful when a type contains internal state that is noisy, expensive to inspect, irrelevant to diagnostics, or sensitive. In the checked example, the real API key is deliberately replaced with a constant placeholder.
A custom Debug implementation should not be treated as a security boundary by itself. If a value must never be exposed, also control where it can be logged, copied, serialized, or otherwise inspected.
debug_struct is not the only builder
Section titled “debug_struct is not the only builder”Rust’s Formatter also provides builders for common Debug shapes:
debug_tuple()for tuple-like outputdebug_list()for sequence-like outputdebug_set()for set-like outputdebug_map()for key-value output
These helpers let a manual implementation keep the conventional Rust Debug style instead of recreating brackets and separators yourself.
You can write a completely custom representation
Section titled “You can write a completely custom representation”If none of the builders match the representation you want, the Debug documentation also permits writing directly to the formatter with write!.
That gives you full control, but it also makes you responsible for the entire representation. Prefer the debug builders when the value is still conceptually a struct, tuple, list, set, or map.
Pretty Debug still works
Section titled “Pretty Debug still works”Manual implementations built with the formatter’s debug builders support {:#?}. You therefore do not need separate compact and pretty implementations.
The same stability warning still applies: Debug output is programmer-facing inspection text, not a serialization format or wire protocol. See pretty Debug for that boundary.
Derive Debug unless you need control
Section titled “Derive Debug unless you need control”The standard library recommends deriving Debug in the ordinary case. Manual Debug is most useful when the derived representation is unavailable or deliberately wrong for diagnostics.
If your problem is simply that the compiler says a type does not implement Debug, start with E0277: type doesn’t implement Debug. If you only want to inspect an ordinary struct, print structs and arrays is the shorter path.