enum に対して PartialOrd を実装したいとします。このとき enum を構成する型すべてに PartialOrd が実装されているならば derive マクロで簡単に実現できます。
こんな感じ:
#[derive(PartialEq, PartialOrd)] // NOTE: PartialOrd には PartialEq も必要。
enum Foo {
A,
B(usize),
C(String),
}
しかし、PartialOrd を実装していない型が含まれる場合は derive することはできません。
これはビルドが通らない:
use hoge_crate::NoPartialOrd; // PartialOrd を実装していない何か
#[derive(PartialEq, PartialOrd)]
enum Foo {
A,
B(usize),
C(String),
D(NoPartialOrd),
}
そんなときは手動で PartialOrd を実装します。
以下のようになるでしょう。
use hoge_crate::NoPartialOrd;
#[derive(PartialEq)]
enum Foo {
A,
B(usize),
C(String),
D(NoPartialOrd),
}
use std::cmp::Ordering;
impl PartialOrd for Foo {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// NOTE: ここで use しておくとコードが短くなって見通しがよい。
use Foo::*;
use Ordering::*;
match (self, other) {
(A, A) => Some(Equal),
(A, B(_)) => Some(Less),
(A, C(_)) => Some(Less),
(A, D(_)) => Some(Less),
(B(_), A) => Some(Greater),
(B(a), B(b)) => a.partial_cmp(b),
(B(_), C(_)) => Some(Less),
(B(_), D(_)) => Some(Less),
(C(_), A) => Some(Greater),
(C(_), B(_)) => Some(Greater),
(C(a), C(b)) => a.partial_cmp(b),
(C(_), D(_)) => Some(Less),
(D(_), A) => Some(Greater),
(D(_), B(_)) => Some(Greater),
(D(_), C(_)) => Some(Greater),
(D(_), D(_)) => Some(Equal),
}
}
}
おっと、match の中で Foo の構成子の組み合わせをすべて列挙しているので長いです!あまり書きたくないですね。
明らかに冗長なので、条件を整理すれば短くできます。
impl PartialOrd for Foo {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use Foo::*;
use Ordering::*;
match (self, other) {
(A, A) => Some(Equal),
(B(a), B(b)) => a.partial_cmp(b),
(C(a), C(b)) => a.partial_cmp(b),
(D(_), D(_)) => Some(Equal),
(B(_), A) | (C(_), A) | (C(_), B(_)) | (D(_), A) | (D(_), B(_)) | (D(_), C(_)) => Some(Greater),
_ => Some(Less),
}
}
}
Some(Less)
を返すアームと Some(Greater)
を返すアームをまとめました。
行数は短くなりましたが、読みづらい上に書くのがめんどくさく、また、バグを埋め込みそうです。
もう少しいい感じに書きたいです。
そこで、Foo
を受け取ってその構成子が何番目かを返す関数 (f
) を導入して、こんな感じにしてみました。
impl PartialOrd for Foo {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use Foo::*;
match (self, other) {
(B(a), B(b)) => a.partial_cmp(b),
(C(a), C(b)) => a.partial_cmp(b),
(a, b) => f(a).partial_cmp(&f(b)), // デフォルトでは関数 `f` の返り値を使って `partial_cmp` する。
}
}
}
fn f(foo: &Foo) -> i32 {
use Foo::*;
match foo {
A => 0,
B(_) => 1,
C(_) => 2,
D(_) => 3,
}
}
だいぶすっきりして読みやすくなりました!よさそう😊
欲を言えば関数 f
を標準で提供してほしいですが、なさそうなのでこれで満足することにします。