Introduction
The #[derive(Debug)] macro automatically generates a Debug implementation, but it requires every field type to also implement Debug. When a struct contains a field whose type does not implement Debug (such as a raw pointer, a trait object, or a type from a third-party crate), the compiler rejects the derive with the trait bound 'FieldType: std::fmt::Debug' is not satisfied. This blocks logging, error reporting, and test debugging.
Symptoms
the trait bound 'FnOnce(): Debug' is not satisfieddyn Trait: Debugnot implemented for trait objects*mut c_void: Debugnot implemented for raw pointersthird_party_crate::Type: Debugnot implemented- Error in generated
fmtmethod from derive macro
Common Causes
- Struct contains trait object (
dyn Trait) without Debug bound - Raw pointers (
*const T,*mut T) do not implement Debug - Third-party types that deliberately do not implement Debug
- Closures (
Fn,FnMut,FnOnce) cannot implement Debug - Generic type parameter without
Debugbound in struct definition
Step-by-Step Fix
- 1.Manually implement Debug for types with non-Debug fields:
- 2.```rust
- 3.use std::fmt;
struct Service { name: String, handler: Box<dyn Handler>, // Does not implement Debug config: AppConfig, // Implements Debug }
// Manual Debug implementation impl fmt::Debug for Service { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Service") .field("name", &self.name) .field("handler", &"<dyn Handler>") // Placeholder for non-Debug field .field("config", &self.config) .finish() } }
// Now this works: let service = Service { ... }; println!("{:?}", service); // Service { name: "api", handler: <dyn Handler>, config: AppConfig { ... } } ```
- 1.Add Debug trait bound to trait objects:
- 2.```rust
- 3.trait Handler: Debug { // All implementors must also be Debug
- 4.fn handle(&self, request: &Request) -> Response;
- 5.}
// Now dyn Handler implements Debug struct Service { name: String, handler: Box<dyn Handler>, // OK: Handler: Debug }
// Derive works now #[derive(Debug)] struct Service { name: String, handler: Box<dyn Handler>, } ```
- 1.Handle raw pointers in Debug impl:
- 2.```rust
- 3.use std::fmt;
struct NativeResource { name: String, ptr: *mut c_void, // Raw pointer, no Debug }
impl fmt::Debug for NativeResource { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("NativeResource") .field("name", &self.name) .field("ptr", &format_args!("{:p}", self.ptr)) // Print pointer address .finish() } } ```
- 1.Add Debug bound to generic type parameters:
- 2.```rust
- 3.// WRONG - T may not implement Debug
- 4.#[derive(Debug)]
- 5.struct Container<T> {
- 6.value: T,
- 7.}
// CORRECT - require T to implement Debug #[derive(Debug)] struct Container<T: Debug> { value: T, }
// Or use where clause (preferred for complex bounds) #[derive(Debug)] struct Container<T> where T: Debug + Clone + Send, { value: T, } ```
- 1.Handle closure fields that cannot be Debugged:
- 2.```rust
- 3.use std::fmt;
struct Callback { name: String, on_complete: Box<dyn Fn(Result<String>)>, // Closures cannot implement Debug }
impl fmt::Debug for Callback { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Callback") .field("name", &self.name) .field("on_complete", &"<closure>") // Just indicate it is a closure .finish() } } ```
Prevention
- Add
Debugas a supertrait to your own traits:trait MyTrait: Debug {} - Use
#[derive(Debug)]as the default for all new structs and enums - Manually implement
Debugwhen a field type is from a crate you do not control - Use
format_args!("{:p}", ptr)to display pointer addresses in Debug output - Add
T: Debugbounds to generic structs that derive Debug - Request upstream crates to implement Debug for their types