Generic implementation variably needs `T: Debug`

I'm playing with the code on this page.
https://doc.rust-lang.org/rust-by-example/generics/where.html

use std::fmt::Debug;

trait PrintInOption {
    fn print_in_option(self);
}

impl<T> PrintInOption for T where
    Option<T>: Debug
{
    fn print_in_option(self)
    { 
        println!("{:?}", Some(self));
    }
}

I wanted a version which wouldn't move the object calling print_in_option, so I changed self to &self:

use std::fmt::Debug;

trait PrintInOption {
    fn print_in_option(&self);
}

impl<T> PrintInOption for T where
    Option<T>: Debug
{
    fn print_in_option(&self)
    { 
        println!("{:?}", Some(self));
    }
}

However, this second version won't compile without an additional trait bound. It needs T: Debug too.

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

I am curious why this extra bound is required based on the change to &self.

The reason the existing bound doesn't work is that Option<&T> is a different type from Option<T>. The reason it asks for T: Debug rather than Option<&T>: Debug is that Option<&T> is Debug if and only if T is Debug, and T: Debug is simpler.

1 Like

I see. Is a version with Option<&T>: Debug salvageable? When I try, I am getting:

error[E0637]: `&` without an explicit lifetime name cannot be used here
  --> r-14.rs:53:12
   |
53 |     Option<&T>: std::fmt::Debug,
   |            ^ explicit lifetime name needed here

The version that works looks like this:

impl<T> PrintInOption for T
where
    for<'a> Option<&'a T>: Debug,
{
    fn print_in_option(&self) {
        println!("{:?}", Some(self));
    }
}

This is necessary because Option<&'a T> and Option<&'b T> are different types. The for<'a> syntax means "for all lifetimes", and is equivalent to this:

impl<T> PrintInOption for T
where
    Option<&'lifetime1 T>: Debug,
    Option<&'lifetime2 T>: Debug,
    Option<&'lifetime3 T>: Debug,
    Option<&'lifetime4 T>: Debug,
    Option<&'lifetime5 T>: Debug,
    Option<&'lifetime6 T>: Debug,
    ...
{
    fn print_in_option(&self) {
        println!("{:?}", Some(self));
    }
}

where the list is infinitely long, going through all possible lifetimes.

(strictly speaking you only need Option<&'that_lifetime_inside_print_in_option T>: Debug, but it is not possible to talk about that lifetime, so we have to instead say it applies to all lifetimes)

3 Likes

We could if we leave the trait as is and instead impl it over a reference:

use std::fmt::Debug;

trait PrintInOption {
    fn print_in_option(self);
}

impl<'a, T> PrintInOption for &'a T
where
    Option<&'a T>: Debug,
{
    fn print_in_option(self) {
        println!("{:?}", Some(self));
    }
}

That's true.