`[derive(Debug)]` puts wrong trait bounds?!

I have types for which I'm deriving Debug.

Now, those types use generics, in particular their fields have types which are generic.
Since the implementation of Debug needs to print these fields, it needs to put some trait bounds, namely it needs to require that the fields (that it needs to print) implement Debug.

For example, for this struct it works mostly as expected:

#[derive(Debug)]
struct Foo<T: FromStr> {
    inner: T::Err,
}

The generated implementation is:

impl<T: Debug + FromStr> Debug for Foo<T>
where
    T::Err: Debug,
{
    ...
}

Note that it requires T::Err : Debug, because it needs to print a T::Err, and this is exactly what I expected.
It also requires T: Debug, which it doesn't need (!), so that's already my first complaint.

But it gets even worse when I add this other type:

#[derive(Debug)]
struct Bar<T: FromStr> {
    inner: Foo<T>,
}

What I would expect, is that for the Debug implementation it would put a bound Foo<T> : Debug, because it needs to print a Foo<T>.
However, this is not what it's doing. Here is the generated implementation:

impl<T: Debug + FromStr> for Bar<T> {
    ...
}

As you can see, there is no where clause, and in fact my program won't compile because as soon as the above implementation will try to print inner: Foo<T>, it complains that the respective Debug implementation doesn't exist.

I don't get why it puts the expected trait bound for the first struct, but not for the second?
And also why it's always requiring T : Debug, which it never needs.
(This might actually lead to problems in my project, because T doesn't implement Debug and only T::Err does.)

Don't you agree with me? Should I file an issue/RFC for that?

Ok I just found out it's an issue which exists since 10 years...

This is horrible. Such a basic thing...

It's not that simple. For one, you need coinduction for perfect derive, which we still don't have (though it's getting closer). For another, changing how derive works is a breaking change. And additionally, perfect derive restricts which changes to your struct are breaking changes more than the derive we have does.[1]

You can read more in this blog post.


  1. The last two together probably means that if we get perfect derive, we'll keep the other form as well, as opposed to changing it over an edition. At least, I hope so. ↩︎

4 Likes

I can't offer a fix for #[derive] but I can offer an alternative:

1 Like