16

In the documentation for Ord, it says

Implementations must be consistent with the PartialOrd implementation [...]

That of course makes sense and can easily be archived as in the example further down:

impl PartialOrd for MyType {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

I wonder, why they would leave this burden / risk to us users instead of having a blanket impl<T: Ord> PartialOrd for T { /* ... */ }.

I tested for problems with circular dependencies and stuff in a playground, but that worked as I would expect it. The internet didn't yield any results either.

Another reason I can think of is how the derive macros work right now. One would probably have to replace every derive(PartialOrd, Ord) with just derive(Ord) (or make the macro for PartialOrd smarter - I don't know if it can become that smart though).

Adding the suggested blanket impl would forbid custom implementations of PartialOrd and eliminate the requirement for consistency. Of course, that would now be a breaking change in the library. Is that the only reason, or am I missing some other arguments here?

1

2 Answers 2

11

It would be a good idea to have a blanket implementation of Ord for types that implement PartialOrd. The reason that such a blanket implementation doesn't exist is because it's not technically possible. Just adding a impl<T: Ord> PartialOrd for T block to core would result in an error:

error[E0119]: conflicting implementations of trait `std::cmp::PartialOrd<&_>` for type `&_`
 --> src/lib.rs:1:1
  |
1 | impl<T: Ord> PartialOrd for T {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<A, B> PartialOrd<&B> for &A
            where A: PartialOrd<B>, A: ?Sized, B: ?Sized;

There is already a blanket implementation in core that implements PartialOrd for all references to PartialOrd types. By implementing PartialOrd for all types that implement Ord, it would be possible to have PartialOrd implemented for the same type twice. It would then be unclear which implementation should be used. Consider this code:

#[derive(PartialEq, Eq)]
struct X;
impl PartialOrd for X {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        panic!("1");
    }
}
impl Ord for &X {
    fn cmp(&self, other: &Self) -> Ordering {
        panic!("2");
    }
}

Here are the traits that get implemented on X

  • X: PartialOrd (directly implemented), panics with 1
  • &X: PartialOrd (blanket implementation for references), panics with 1
  • &X: Ord (directly implemented), panics with 2

But if we were to implement PartialOrd automatically, then there would be an additional implementation of PartialOrd on &X that panics with 2. It is ambiguous how it should be resolved.

The solution to this is to use specialization, which is an unstable feature in Rust, that didn't even exist when Rust 1.0 was released. As such, not implementing PartialOrd was the only option. Specialization allows the same item to have multiple conflicting trait implementations in a way that is unambiguous, but it is currently unstable, unsound, and nightly only. See this Rust issue for more details on the issue.

1

Apparently, there is a reference to that, in a github issue - rust-lang/rust#63104:

This conflicts with the existing blanket impl in core.

error[E0119]: conflicting implementations of trait `std::cmp::PartialOrd<&_>` > for type `&_`:
 --> src/lib.rs:3:1
  |
3 | impl<T: Ord> PartialOrd for T {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<A, B> std::cmp::PartialOrd<&B> for &A
            where A: std::cmp::PartialOrd<B>, A: ?Sized, B: ?Sized;

Not the answer you're looking for? Browse other questions tagged or ask your own question.