Trait bounds on associated types only - is it possible?


I am struggling to use associated types in a trait where the associated type does fullfil some requirements (e.g. Send or Clone or PartialOrd).

In particular I have a (relatively complex) trait with several associated types. Some of the associated types are required to implement other traits that the main trait does not implement. However, the compiler complains that the outer trait does not implement implement those associated-required-traits - which is by intention and should not be required?

An example is likely clearer: ( Rust Playground )

pub trait Associated: PartialEq + PartialOrd {}

pub trait Trait {
    type A: Associated;

    fn test() -> Self;

#[derive(PartialEq, PartialOrd)]
pub struct Usage<N: Trait>(N::A);

pub fn test<T: Trait>(a: &Usage<T>, b: &Usage<T>) -> bool {
    a < b

Is this by design or am i misunderstanding something here? Is there any other way to type this out such that the compiler will not complain?

My main usage of this pattern is to reduce the number of generic arguments needed (e.g. lots of boilerplate) for all types and functions in my codebase (this pattern allowing me to use 1 instead of 5).

I think you meant to write:

pub fn test<T: Trait>(a: &Usage<T>, b: &Usage<T>) -> bool {
-    a < b
+    a.0 < b.0

Nope - I explicitly did not want to write that :slight_smile:

I have derived PartialOrd for Usage - so it should work without that should it not?

( And not in particular the compiler error - complaining that adding

+ std::cmp::PartialOrd

to the the function sugnature solves the problem (it does) - but why should T need be PartialOrd? Only T::A should need to be and is by the definition in the trait that allready.

The #[derive] implementation sees that your type has a generic parameter, so it assumes it contains a field of that type. Therefore, it adds a where N: PartialOrd constraint to impl PartialOrd for Usage<N>. It is kind of naïve in this regard – newer derive macros actually add bounds only for the types contained in fields, but std can't be fixed because it would be breaking.

If you don't want this behavior, implement PartialOrd (and viz. the other traits) manually. This makes your example compile. Although as I note in the comments that I'm not sure how you think comparisons should work without N::A also being PartialEq/PartialCmp.

1 Like

Thank you!

That explains a lot (but I fail to understand how that would be a breaking change - with that change all things for which e.g. PartialOrd was previously correctly derived would be after the change too).

I do not expect it to work without A being PartialOrd (and it is since I require it in the trait definition on line 3 in you example).

My usage is basically that I have an outer trait that cannot be Clone, but is use some of the associated types in ways that require Clone - and I could not get it to compile.

Your work-around will work, but it will be a lot if manual Clone implementations for me - so it's unfortunate that the derive macro works as it does.

It would potentially result in new impls (even blanket impls, I think), which could conflict with existing impls.