Why don't `Eq` and `Ord` have a `Rhs` parameter (anymore)?

I see that way back (pre-1.0) when Stabilize cmp by aturon · Pull Request #20065 · rust-lang/rust · GitHub was merged, it removed the Rhs type parameter from the Eq and Ord traits but not from the PartialEq and PartialOrd traits:

However, RFC 439 that it was apparently implementing specifically proposed a Rhs parameter for those traits. Neither in the discussions around the RFC nor those around the stabilisation PR can I see very much that explains why the parameter was removed: all I have found that are even vaguely relevant are @alexcrichton's review comment ("I'm not sure that any of these properties can possibly hold if I implement Eq<U> for T where T != U") and @eddyb's RFC comment ("I believe implementations of Eq and Ord where the two sides aren't the same type are more prone to incorrectness"), but neither were arguing for removal of the parameter.

Can anyone remember why these parameters were removed? If T: PartialOrd<U> (which necessitates that U: PartialOrd<T>) and all T are comparable with all U, then do I not have a total order instead of just a partial one? Why won't Rust allow me to express that with T: Ord<U> and U: Ord<T>? (Likewise for Eq).

2 Likes

I can’t tell you about history, but I can throw in my 2 cents on why this restriction may or may not be sensible.

First note that in mathematical terms, the terminology “total order” or “partial order” both only are used to describe binary relations on a single set, i.e. always corresponding to the situation where the left-hand-side and right-hand-side types are the same. So that terminology is irrelevant for discussing this question.

If Eq is interpreted as a notion of “mathematical equality” (not just an “equivalence relation”), then a “partial order” corresponds to T: Eq + PartialOrd, and a “total order” corresponds to T: Ord. That’s a lot of conditions to get a good correspondence, and PartialEq is completely out of the equation nonetheless, so let’s not focus too much on mathematical terminology.


In terms of actual documented requirements, the only thing that differentiates T: Eq from T: PartialEq<T> is the additional requirement that x == x should be true for all x.

Now, imagine we had some sort of T: Eq<U> trait. What would it’s additional requirement over T: PartialEq<U> be when T and U are distinct types? It cannot be x == x, because x cannot be both of type T and U at the same time. The only option I can think of would be to have no additional requirements at all. Possibly that seemed to use-less of an approach, so it wasn’t followed?

For Eq and PartialEq in isolation, Eq with different left-hand-side and right-hand-side types is not very useful. On the other hand, since Ord requires Eq, restricting Eq to operate on a single type transfers to Ord as well. And for Ord, there is an additional requirement that still makes sense even when the types are different, i.e. that partial_cmp never fails.

So I conclude that there might indeed be some sense in the idea of generalizing Eq to have a Rhs parameter, too. One reasonable extra requirement for Eq itself over PartialEq that I just thought of could probably be that T: Eq<U> implementations are expected to only exist when T: Eq<T> and U: Eq<U> implementations exist as well. Similarly for Ord, I guess… I’m not a big fan of these “such and such implementations must exist” requirement in the first place, because it’s unclear (to me) how exactly they should be upheld, but that’s a separate discussion.


In case the question is whether the extra Rhs parameter could be added to Eq now, i.e. whether that’s a breaking change, on first thought, I would say … probably not breaking!? … unless I’m missing something.

5 Likes

I don't know how much that matters, since one could look at a bunch of Rust types as using the Ord on the same mathematical type.

For example, we could say that i32: Ord<u32> is the "total order" on ℝ (or ℤ), even though neither type can represent all possible values in those sets.

8 Likes

This sounds like it has overlaps with range types where the operations "exist" on the "upper" type, just that the potential values on either side come from different domains.

I've looked into this before and here are a few breadcrumbs.

Arguably it's already approved and just no longer fully implemented since it was part of the accepted RFC and the stabilization PR lies about matching the RFC :slightly_smiling_face:.

1 Like

It's now possible to have unstable type parameters, if they have defaults, so one way forward might just make a PR doing that and see what libs-api has to say.

Seems plausible to experiment with it, at least.

3 Likes

There was some discussion about the weirdness of these type parameters in another thread on this forum which I think is quite relevant:

Musings on PartialEq

2 Likes