10
votes

It is considered good practice to #[derive(Debug)] for most structs you create to aid in debugging. However, this is not possible if your struct contains a type without Debug, such as traits. But if the trait is under my control, is there something I can do to let users' implementations of said trait show up in the debug message?

I could require that people who implement my trait also implement Debug, but I don't like having to add that arbitrary requirement:

trait MyTrait: Debug { ... }

I could just implement Debug for my trait:

trait MyTrait { ... }

impl Debug for MyTrait {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "MyTrait {{ ... }}")
    }
}

This doesn't allow implementations to override Debug - it's almost as if the function is not virtual. How can I make this work?

use std::fmt;
use std::fmt::{ Formatter, Debug };

#[derive(Debug)]
struct A {
    a: Box<Data>,
}

trait Data {}

impl Debug for Data {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "Data{{ ... }}")
    }
}

#[derive(Debug)]
struct B(i32);

impl Data for B {}

fn main() {
    let a = A{ a: Box::new(B(42)) };
    println!("{:?}", a);
}

Outputs:

A { a: Data{ ... } }

What I want:

A { a: B(42) }

I only want the first output when B does not implement Debug.

1
Sounds like Allow a trait to implement its parent trait is what you are after with a default impl of Debug for your trait.Lukazoid

1 Answers

6
votes

You can create your own trait method. Types that wish to have enhanced debugging and implement Debug can delegate:

use std::fmt;
use std::fmt::{ Formatter, Debug };

#[derive(Debug)]
struct Container(Box<Data>);

trait Data {
    fn debug_fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "Data {{ ... }}")
    }
}

impl Debug for Data {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        self.debug_fmt(f)
    }
}

#[derive(Debug)]
struct Overrides(i32);

impl Data for Overrides {
    fn debug_fmt(&self, f: &mut Formatter) -> fmt::Result {
        self.fmt(f)
    }
}

#[derive(Debug)]
struct Defaults(i32);
impl Data for Defaults {}

fn main() {
    let a = Container(Box::new(Overrides(42)));
    println!("{:?}", a);
    let a = Container(Box::new(Defaults(42)));
    println!("{:?}", a);
}

An alternate solution that requires the unstable specialization feature:

#![feature(specialization)]

use std::fmt;
use std::fmt::{Formatter, Debug};

struct Container<D>(Box<D>) where D: Data;

impl<D> Debug for Container<D>
    where D: Data
{
    default fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "Container(Data {{ ... }})")
    }
}

impl<D> Debug for Container<D>
    where D: Data + Debug
{
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "Container({:?})", self.0)
    }
}

trait Data {}

#[derive(Debug)]
struct Overrides(i32);
impl Data for Overrides {}

struct Defaults(i32);
impl Data for Defaults {}

fn main() {
    let a = Container(Box::new(Overrides(42)));
    println!("{:?}", a);
    let a = Container(Box::new(Defaults(42)));
    println!("{:?}", a);
}

Note that this places the burden on the container.