How to sort enum variants?

I'd like to implement PartialOrd for an enum where the inner value shall not be taken into account:

#[derive(PartialOrd, Ord, PartialEq, Eq)]
enum E {
    First(u32),
    // potentially some more variants
    Last(u32),
}

This doesn't work because the inner u32 will be recursively evaluated due to the derive. std::mem::discriminant doesn't help because it's not [Partial]Ord.

I peeked into the macro expansion to see how the derive was implemented. I found core::intrinsics::discriminant_value which is unavailable to normal code.

I know two workarounds:

  • implement a private my_discriminant(&self) -> u8 and hard-code the discriminants
  • implement [partial_]ord() with an exhaustive match (self, other) { ā€¦ }

I had a similar problem before, trying to find the minimum of two Options (where None should come first). As there were only two variants it was trivial to work around.

Is there a nicer solution?

2 Likes

It doesn't seem to me like the derive evaluates the inner u32. The following runs without panicking:

#[derive(PartialOrd, Ord, PartialEq, Eq)]
enum E {
    First(u32),
    // potentially some more variants
    Last(u32),
}

fn main() {
    assert!(E::First(50) < E::Last(24));
    assert!(E::First(24) < E::Last(50));
}

Playground link: Rust Playground


From std::cmp::PartialOrd:

When derive d on enums, variants are ordered by their top-to-bottom discriminant order.

How about discriminant in std::mem - Rust ?

They're taken into account as second criterion.

This will pass but shouldn't. (if the inner value was ignored they should be equal)

See my comment (sorry there was a typo) - I don't think they can be compared using ord. Or did I miss something?

You could define a wrapper type Unordered<T> that always returns None from PartialOrd::cmp, and then store Unordered<u32>s in the enum in place of the undecorated u32s.

(On mobile right now, or else Iā€™d type an example)

Yes, that would be another workaround. Ergonomics would suffer, though.

I was able to find this closed issue that talks about implementing Ord for mem::Discriminant: https://github.com/rust-lang/rust/issues/51561
Scottmcm brought up a good point against doing so, but the result seems to be that we don't currently have an easy and ergonomic way* to compare discriminants in stable rust.

*The boilerplate would probably be easy to do with proc macros though. But bringing in proc macros just for this is less than ideal.

Thank you for the reference!

This might be the conclusion:

this seems rare enough that it's not worth landing in std.

The odd thing is that enums can be ordered (using derive) - you can just not stop that from taking the inner types into account.

You could follow that suggestion (wrapping in Unordered<T>) on a private, otherwise unused version of your struct, and then implement the traits yourself in terms of the other struct.

I think you mean the enums.
Regardless, that requires making sure that the other enum is always up to date with the real enum. But if you're gonna copy the variants into the other enum by hand, at that point you might as well write
the my_discriminant(&self) -> u8 instead.

1 Like

Yeah, that's true, and silly for another reason to boot. If mirroring the enum was the direction taken, just leaving out the payloads altogether would have the same effect. I've used a crate that could do the mirroring (creates a new enum with the same names but no payloads), but I don't know that it's really worth it here.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.