Is `None` returned from `partial_cmp` supposed to only signify `NaN`?

I have an enum:

``````enum Foo {
A(u32),
B(u32),
}
``````

And I wonder if it makes sense to implement `PartialOrd`. Comparing `A` to `B` is impossible without external information but they can in principle be compared. Thus the derived implementation is confusing at best. To not bother you with domain knowledge, same problem would occur if `A` and `B` was `Eur` and `Usd` - you can compare them if you know the exchange rate.

So it seems the implementation could return `None` for distinct variants and `Some(_)` for same variants comparing inner values. Similarly for `PartialEq`.

What I'm not sure is if this implementation is logically sound. IOW, I know `partial_cmp() -> Option` and all those traits were designed to reasonably handle floats/NaNs. Can it be reasonably used in my case too or should it only be used for NaN-like situations?

1 Like

Yes. `PartialOrd` in Rust has the exact same meaning as in mathematics when you say "partial order on a set", aka, an ordering exists only between some elements.

6 Likes

Clearly explained, thanks!

It’ a bit more general than that mathematical notion; admitted it’s still (mis-)documented as being about a “partial order”. I’ve semi-recently participated in a way too long discussion about those points; I don’t fully remember the outcome, I think the main differences were that

• it’s not clear what notion of “equality” to work with in the context of programming, and depending on how you’re interpreting things, you could be talking about e.g. interpreting `Ord` as a weak ordering, and `PartialOrd` together with `Eq` would be a preorder
• `PartialOrd` with only `PartialEq` and not `Eq` allows a value to be unequal to itself, which is completely different from any typical “ordering” relations in mathematics

On the other hand `PartialOrd` can be used for “partial orders” too, just it also allows even more other thing beyond “partial orders”. For your use case, this means that @RedDocMD's answer is still correct, you can use it. You can even implement `Eq` as well if you have a “partial order”, and you can use “partial order”-axioms to verify that you have, in particular, a valid `PartialOrd` implementation, too.

Also note that equality checking via `PartialEq` would still always return `true` or `false`; there’s no way to make that, too return `None`, even though one could argue that comparing an `A` and a `B` for equality should give an answer “unknown!” rather than “no!”, too. I guess it isn’t too bad though, just use `partial_cmp`.

9 Likes

That caveat also applies to `PartialOrd::{lt, le, gt, ge}`. These all return `false` when `partial_cmp` returns `None`. So for example, you should be careful that `!lt` does not imply `ge`.

2 Likes

I wish `partial_cmp` returning `None` was far more common, TBH. I've never seen a solid justification for why `Ok` is always less than `Err`, for example, as opposed to the other way around. And needing to have the full n² set of arms in comparisons in order to be total (as opposed to n+1 for partial) has always felt unfortunate to me.

So from me on just having your `partial_cmp` be

``````match (x, y) {
(A(x), A(y)) => x.partial_ord(y),
(B(x), B(y)) => x.partial_ord(y),
_ => None,
}
``````
2 Likes

The difference is that while `PartialOrd::{lt, le, gt, ge}` may all return `false` at the same time, the same is not true for `PartialOrd::{eq, ne}`, unless you’re violating the conditions explained in its documentation:

Implementations must ensure that `eq` and `ne` are consistent with each other:

• `a != b` if and only if `!(a == b)` (ensured by the default implementation).

The only thing differentiating `PartialEq` from `Eq` is that the latter requires `x == x` to be always true while for `PartialEq` it can (and is in case of NaN) be false, and that `PartialEq` can work between different types, while for `Eq` the left-hand-side and right-hand-side argument needs to be of the same type.

The problem is that `Ord` (together with `PartialOrd`) serves multiple purposes

• to provide a semantic notion of ordering that makes sense for the given type
• under this aspect, ordering `Ok` vs. `Err` doesn’t make too much sense
• to offer an implementation of some arbitrary fixed ordering for search-trees such as `BTreeMap`/`BTreeSet`
• for this, having an `Ord` implementation for `Result` is super useful, and the arbitrary choice of how to order `Ok` vs `Err` doesn’t matter much, it just has to be decided either way and then it becomes mostly an irrelevant implementation detail anyway (ignoring `BTree…` operations like `range` or `split_off` where the ordering does matter)
• to offer an overloading of `<`/`<=`/`>`/`>=` operators
• for this, it’s kind-of redundant/overkill/limiting to have any constraints on their implementations listed at all on the respective traits; after all, operators like `+` allow any arbitrary operation, too, and don’t document any laws those should uphold, such as `(a + b) + c) == a + (b + c)` and the like; even the fact that they always need to return `bool` seems somewhat limiting
4 Likes

I agree with what you wrote, but

this is the kind of thing where I get frustrated with it.

And there's lots of places where it ends up causing weird asymmetry, like `.fold(None, cmp::min)` and `.fold(None, cmp::max)` do completely different things. For all that NAN can be annoying, at least `.fold(NAN, f32::min)` and `.fold(NAN, f32::max)` do analogous things.

Without having read the context of this thread, I would like to quote my point on whether: