LoginSignup
3

More than 3 years have passed since last update.

Rust で enum に対して手で PartialOrd を実装する方法について考えてみた

Last updated at Posted at 2020-12-09

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 を標準で提供してほしいですが、なさそうなのでこれで満足することにします。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3