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 satisfied
  • dyn Trait: Debug not implemented for trait objects
  • *mut c_void: Debug not implemented for raw pointers
  • third_party_crate::Type: Debug not implemented
  • Error in generated fmt method 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 Debug bound in struct definition

Step-by-Step Fix

  1. 1.Manually implement Debug for types with non-Debug fields:
  2. 2.```rust
  3. 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. 1.Add Debug trait bound to trait objects:
  2. 2.```rust
  3. 3.trait Handler: Debug { // All implementors must also be Debug
  4. 4.fn handle(&self, request: &Request) -> Response;
  5. 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. 1.Handle raw pointers in Debug impl:
  2. 2.```rust
  3. 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. 1.Add Debug bound to generic type parameters:
  2. 2.```rust
  3. 3.// WRONG - T may not implement Debug
  4. 4.#[derive(Debug)]
  5. 5.struct Container<T> {
  6. 6.value: T,
  7. 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. 1.Handle closure fields that cannot be Debugged:
  2. 2.```rust
  3. 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 Debug as a supertrait to your own traits: trait MyTrait: Debug {}
  • Use #[derive(Debug)] as the default for all new structs and enums
  • Manually implement Debug when 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: Debug bounds to generic structs that derive Debug
  • Request upstream crates to implement Debug for their types