Is it possible to provide 'more specific' `PartialOrd` implementations for some `Struct<T, V, ...>` depending on which type parameters implement `PartialOrd`?

I have an item class like so:

struct Item<First, Second> {
    first: First,
    second: Second,
}

I want to implement PartialOrd (and also Ord) for this class for lexicographical ordering. This, at the very least, requires First: PartialOrd:

impl<First, Second> PartialOrd for Item<First, Second>
    where First: PartialOrd
{
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.first.partial_cmp(other.first)
    }
}

However, if it's also the case that Second: PartialOrd, then I'd ideally like to use second as a discriminant(?) to pick between items with the same first. For instance, sorting some items might look like this:

[
  Item { first: "a", second: "a" },
  Item { first: "a", second: "b" },  // 'aa < ab'
  Item { first: "x", second: "k" },
  Item { first: "x", second: "z" },
  Item { first: "z", second: "z" },  // 'xk < xz < zz'
]

Is this possible in Rust?

My first approach was to add a separate implementation where First: PartialOrd, Second: PartialOrd, but this of course results in conflicting implementations. I've read that specialisation may be of help, but I was hoping for a non-Nightly solution.

1 Like

It’s not possible. The closes you could do is e.g. adding a 3rd type parameter as a sort of “flag” to explicitly decide whether or not to make use of Second.

Edit: Here’s a demo/example  (> playground <)   ₍^. .^₎⟆
though now that I’m reading it, the suggestion of @scottmcm beloy may be a little more straightforward :slight_smile:

1 Like

Whenever the question is "do something different depending on whether a type implements a trait", that's specialization and the answer is that you can't.

The solution is generally to find a way to reframe it as "it always implements a trait but that implementation does something different for different types".

Here you might, for example, insist that Second: PartialOrd always, but also provide a IgnoredInOrd newtype that's always vacuously equal.

Thus instead of Item<u32, NotOrd> you'd have Item<u32, IgnoredInOrd<NotOrd>>.


(This is similar in idea to how you can't have optional fields, but you can have a field of type () that takes no space.)

7 Likes