PartialOrd of Rust Enums based on encapsulated data

I have an Enum the encapsulates numeric primitives (u8, i8, u16, i16, u32, i32, u64, i64, f32, f64) into a common type called "Number". I want to implement a PartialOrd train for the enum based on the encapsulated data allowing different numbers to be compared. I have a solution that uses nested pattern matching and casting, but it seems unwieldy. Is there a better way to do this?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7edcbe456847fb129475ee40568d21c2

use std::cmp::Ordering;

#[derive(PartialEq)]
enum Number {
U8(u8),
I8(i8),
U16(u16),
I16(i16)
}

impl PartialOrd for Number {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// self.height.partial_cmp(&other.height)
match self {
Number::U8(x) => {
match other {
Number::U8(y) => (*x).partial_cmp(y),
Number::I8(y) => (*x as i16).partial_cmp(&(*y as i16)),
Number::U16(y) => (*x as u16).partial_cmp(y),
Number::I16(y) => (*x as i32).partial_cmp(&(*y as i32)),
}
},
Number::I8(x) => {
match other {
Number::U8(y) => (*x as i16).partial_cmp(&(*y as i16)),
Number::I8(y) => (*x).partial_cmp(y),
Number::U16(y) => (*x as u16).partial_cmp(y),
Number::I16(y) => (*x as i32).partial_cmp(&(*y as i32)),
}
},
Number::U16(x) => {
match other {
Number::U8(y) => (*x).partial_cmp(&(*y as u16)),
Number::I8(y) => (*x as i32).partial_cmp(&(*y as i32)),
Number::U16(y) => (*x).partial_cmp(y),
Number::I16(y) => (*x as i32).partial_cmp(&(*y as i32)),
}
},
Number::I16(x) => {
match other {
Number::U8(y) => (*x).partial_cmp(&(*y as i16)),
Number::I8(y) => (*x).partial_cmp(&(*y as i16)),
Number::U16(y) => (*x as i32).partial_cmp(&(*y as i32)),
Number::I16(y) => (*x).partial_cmp(y),
}
},
}
}
}


You can match on a tuple, which flattens everything:

impl PartialOrd for Number {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Number::U8(x), Number::U8(y)) => (*x).partial_cmp(y),
(Number::U8(x), Number::I8(y)) => (*x as i16).partial_cmp(&(*y as i16)),
(Number::U8(x), Number::U16(y)) => (*x as u16).partial_cmp(y),
(Number::U8(x), Number::I16(y)) => (*x as i32).partial_cmp(&(*y as i32)),

(Number::I8(x), Number::U8(y)) => (*x as i16).partial_cmp(&(*y as i16)),
(Number::I8(x), Number::I8(y)) => (*x).partial_cmp(y),
(Number::I8(x), Number::U16(y)) => (*x as u16).partial_cmp(y),
(Number::I8(x), Number::I16(y)) => (*x as i32).partial_cmp(&(*y as i32)),

(Number::U16(x), Number::U8(y)) => (*x).partial_cmp(&(*y as u16)),
(Number::U16(x), Number::I8(y)) => (*x as i32).partial_cmp(&(*y as i32)),
(Number::U16(x), Number::U16(y)) => (*x).partial_cmp(y),
(Number::U16(x), Number::I16(y)) => (*x as i32).partial_cmp(&(*y as i32)),

(Number::I16(x), Number::U8(y)) => (*x).partial_cmp(&(*y as i16)),
(Number::I16(x), Number::I8(y)) => (*x).partial_cmp(&(*y as i16)),
(Number::I16(x), Number::U16(y)) => (*x as i32).partial_cmp(&(*y as i32)),
(Number::I16(x), Number::I16(y)) => (*x).partial_cmp(y),
}
}
}


Whether this is better than nested matches is a matter of taste, I reckon.

Another way you could simplify your implementation is converting each argument to another (possibly private) enum type with fewer cases (e.g. i64, u64 and f64 should be enough), then compare pairs of this second enum type (which only yields 2^3 = 8 cases, instead of 2^10 = 1024 cases). In fact, since the size of Number is determined by its largest member, depending on your use case, it might make more sense to only include i64, u64 and f64 in the public Number enum.