Why isn’t there an std::cmp::PartialEq<Self> bound on the RHS type parameter in std::cmp::PartialEq?

std::cmp::PartialEq states “that the trait itself must be implemented symmetrically and transitively: if T: PartialEq<U> and U: PartialEq<V> then U: PartialEq<T> …” (emphasis added), but this is not actually enforced in its definition. I am sure I am missing something obvious, so can someone enlighten me as to why RHS doesn’t have the trait bound std::cmp::PartialEq<Self> in addition to its ?Sized bound?

1 Like

Whenever I've tried to implement that sort of circular bound chain, it confused the trait solver and threw it into an infinite loop. Perhaps it's just a limitation of the current implementation, and will work in the future. Or maybe I was just unlucky.

Good question! I would have thought that we would be hitting a circular bound in that case, but

trait Trait<U : ?Sized + Trait<Self> = Self> {}

enum A {}
impl Trait for A {}

enum B {}
impl Trait<A> for B {}
impl Trait<B> for A {}

compiles just fine, even on rust 1.0.0.


EDIT: yeah, the problem lies on the ergonomics of somebody using that generic bound: the constraint is seen to be required everywhere, instead of required on impl but assumed on use (like supertraits do). That is, having a <T, U : Trait<T>> generic fails with the missing T : Trait<U> bound.

This means that every (non-reflexive) usage of that bound would have ended up duplicated... which seems too cumbersome to be worth it.

4 Likes

I can be rather masochistic when it comes to “correctness” and fail to care about ergonomics as much as I should. I am working on that. Thank you for your reply.

P. S. Not really related, but I recently stumbled on one of your questions that I found to be useful as I was confused as to when one should favor supertraits over generic implementations.

1 Like