Type Requirements on Generics Weirdly Enforced

It doesn't seem like the compiler is enforcing type requirements on derive macros properly. For example, this does not compile, but seems like it should because Test derives Debug which necessitates that T is Debug (playground):

#[derive(Debug)]
struct Test<T> {
    a: T
}

trait PrintTest<T> {
    fn print(self) -> Test<T>;
}

impl <T> PrintTest<T> for Test<T> {
    fn print(self) -> Test<T> {
        println!("{:?}", self);
        self
    }
}

Results in:

error[E0277]: `T` doesn't implement `std::fmt::Debug`
  --> src/lib.rs:14:26
   |
14 |         println!("{:?}", self);
   |                          ^^^^ `T` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`

Whereas, this does compile, and I wouldn't think it should (playground):

#[derive(Debug)]
struct Test<T> {
    a: T
}

struct NoDebugType {}

fn main() {
    let t = Test::<NoDebugType>{ a: NoDebugType{} };
    
    // println!("{:?}", t);
}

If you uncomment the println! line, then it will fail with:

error[E0277]: `NoDebugType` doesn't implement `std::fmt::Debug`
  --> src/main.rs:12:22
   |
12 |     println!("{:?}", t);
   |                      ^ `NoDebugType` cannot be formatted using `{:?}`

Is this a bug, or the way it's supposed to work? It's the first one of these that's tripping me up. I'm trying to build a trait for Result and want to print self however it tells me it the two types don't necessarily implement Debug even though Result derives Debug which would necessitate the type parameters being Debug. Thoughts? Thanks!

This is how it's supposed to work. The impl it generates looks like this:

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

struct Test<T> {
    a: T
}

impl<T: Debug> Debug for Test<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Test { a: ref __self_0_0 } => {
                let mut debug_trait_builder = f.debug_struct("Test");
                let _ = debug_trait_builder.field("a", &&(*__self_0_0));
                debug_trait_builder.finish()
            }
        }
    }
}

In particular notice that it says impl<T: Debug>. This means that the impl block only applies when T implements Debug.

If you want to require that T implements Debug, you can do this.

#[derive(Debug)]
struct Test<T: Debug> {
    a: T
}

However I recommend avoiding trait bounds on struct definitions. Try this instead:

impl<T: Debug> PrintTest<T> for Test<T> {
    fn print(self) -> Test<T> {
        println!("{:?}", self);
        self
    }
}
1 Like

Ah, so the derive macro is adding an additional type constraint on the generic T that it too must be Debug. So it's legal to create an instance of Test without T: Debug, but the implementation only works when T implements Debug.

Not super-intuitive to me, but it makes more sense with the derive macro desugared... thanks!

You're welcome. It may not be what is immediately intuitive, but I can promise you that this way is more convenient.

Derive macros can't modify the declarations they are attached to. They can only generate additional code.

Furthermore, not allowing a type to be created unless type bounds are met is a great anti-pattern – you violate the "you only pay for what you use" principle. It's even recommended somewhere in the Book or in the Rust API Guidelines that generic types should only apply trait bounds inside impl blocks, not in the declaration itself.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.