Summary

Add a partial_cmp method to PartialOrd, analogous to cmp in Ord.

Motivation

The Ord::cmp method is useful when working with ordered values. When the exact ordering relationship between two values is required, cmp is both potentially more efficient than computing both a > b and then a < b and makes the code clearer as well.

I feel that in the case of partial orderings, an equivalent to cmp is even more important. I’ve found that it’s very easy to accidentally make assumptions that only hold true in the total order case (for example !(a < b) => a >= b). Explicitly matching against the possible results of the comparison helps keep these assumptions from creeping in.

In addition, the current default implementation setup is a bit strange, as implementations in the partial equality trait assume total equality. This currently makes it easier to incorrectly implement PartialOrd for types that do not have a total ordering, and if PartialOrd is separated from Ord in a way similar to this proposal, the default implementations for PartialOrd will need to be removed and an implementation of the trait will require four repetitive implementations of the required methods.

Detailed design

Add a method to PartialOrd, changing the default implementations of the other methods:

pub trait PartialOrd {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering>;

    fn lt(&self, other: &Self) -> bool {
        match self.partial_cmp(other) {
            Some(Less) => true,
            _ => false,
        }
    }

    fn le(&self, other: &Self) -> bool {
        match self.partial_cmp(other) {
            Some(Less) | Some(Equal) => true,
            _ => false,
        }
    }

    fn gt(&self, other: &Self) -> bool {
        match self.partial_cmp(other) {
            Some(Greater) => true,
            _ => false,
        }
    }

    fn ge(&self, other: &Self) -> bool {
        match self.partial_cmp(other) {
            Some(Greater) | Some(Equal) => true,
            _ => false,
        }
    }
}

Since almost all ordered types have a total ordering, the implementation of partial_cmp is trivial in most cases:

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

This can be done automatically if/when RFC #48 or something like it is accepted and implemented.

Drawbacks

This does add some complexity to PartialOrd. In addition, the more commonly used methods (lt, etc) may become more expensive than they would normally be if their implementations call into partial_ord.

Alternatives

We could invert the default implementations and have a default implementation of partial_cmp in terms of lt and gt. This may slightly simplify things in current Rust, but it makes the default implementation less efficient than it should be. It would also require more work to implement PartialOrd once the currently planned cmp reform has finished as noted above.

partial_cmp could just be called cmp, but it seems like UFCS would need to be implemented first for that to be workable.

Unresolved questions

We may want to add something similar to PartialEq as well. I don’t know what it would be called, though (maybe partial_eq?):

// I don't feel great about these variant names, but `Equal` is already taken
// by `Ordering` which is in the same module.
pub enum Equality {
    AreEqual,
    AreUnequal,
}

pub trait PartialEq {
    fn partial_eq(&self, other: &Self) -> Option<Equality>;

    fn eq(&self, other: &Self) -> bool {
        match self.partial_eq(other) {
            Some(AreEqual) => true,
            _ => false,
        }
    }

    fn neq(&self, other: &Self) -> bool {
        match self.partial_eq(other) {
            Some(AreUnequal) => true,
            _ => false,
        }
    }
}