Where clause in trait definition must be copied to where the trait is used?

I wrote up this short example, which doesn't compile:

use serde::Deserialize;

trait Refer {
    type Ref<'a>;
}

// example
impl Refer for String {
    type Ref<'a> = &'a str;
}

trait DeserRefer: Refer 
    where for<'de> Self::Ref<'de>: Deserialize<'de> {}
impl<T: Refer> DeserRefer for T
    where for<'de> Self::Ref<'de>: Deserialize<'de> {}
    
fn test<T: DeserRefer>(t: T) {

}

playground

The issue in this example is that, despite the DeserRefer requiring that the implementor's Ref implement Deserialize, this requirement is for some reason repeated for test()- i.e. the where clause in the definition of DeserRefer needs to be repeated in the declaration of test(). This is a different behavior than I would expect- I would never expect the compiler to require that test() have a where clause, in addition to the constraint placed on T. Shouldn't the constraint T: DeserRefer be enough? Is this a trait solver bug?

1 Like
trait Foo: Bar {}

impl<T> Foo for T {}

Would you expect this to compile? If yes, why, and how do you think it should behave when T: !Bar?

If not, does it make it clear why your (more complicated but essentially identical) code doesn't compile?

(Hint: requirements never change into promises. Therefore they naturally propagate. A trait bound doesn't cause any implementations in itself.)

1 Like

This is a case of non-supertrait bounds not being elaborated elsewhere. That is, here:

trait DeserRefer: Refer 
    where for<'de> Self::Ref<'de>: Deserialize<'de> {}

The where clause is not considered supertrait bound (a bound on Self).[1]

If you don't have to worry about MSRV, we recently got the ability to make bounds on associated types of Self into bounds on Self, thus making them supertrait bounds which are implied elsewhere:

// Inline associated type bound  vvvvvvvvvvvvvvvvvvvvvvvvvv
trait DeserRefer: for<'de> Refer<Ref<'de>: Deserialize<'de>> {}

A few words about "well why didn't it just do that for me?!".

One (very large) consideration with implied bounds, such as supertrait bounds which you don't have to repeat elsewhere, is that removing the bound from the trait becomes a breaking change. In contrast, with your OP, the trait author could remove the bound without breaking downstream code.

Therefore, implied bounds is not something you always want, and not something that can be inflicted on the ecosystem in good conscious (e.g. automatically converting all pre-existing, non-supertrait associated type bounds involving Self to be supertrait bounds). Instead it should be something the author opts into.

Given a non-implied bound on a trait definition, the trait can

  • Remove the bound
    • But then can never add it back, implied or not
  • Make it implied[2]
    • But then can never remove it or make it non-implied

(where "can" and "can never" are short for "non-breaking change" and "breaking change").


  1. Despite the jargon, "supertrait" bounds can also be lifetime bounds. ↩︎

  2. as far as is possible in the language ↩︎

3 Likes

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.