Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is `PartialOrd` not blanket-implemented for all types that implement `Ord`?

Tags:

rust

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?

like image 637
Niklas Mohrin Avatar asked Dec 18 '25 09:12

Niklas Mohrin


1 Answers

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.

like image 76
Smitop Avatar answered Dec 21 '25 03:12

Smitop



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!