Rustでgenericなenumを作る

パッと出てこなかったので。

例えば、位置か速度か力かわからないベクトルがあるとする。すると、Rustならenumにするだろう。

enum CoordKind {
    Position{x:f64, y:f64, z:f64},
    Velocity{x:f64, y:f64, z:f64},
}

let pos = CoordKind::Position{x: 1.0, y: 2.0, z: 3.0}

これをf64でもf32でも使いたいとなると、ジェネリクスを使うことになる。

enum CoordKind<T> {
    Position{x:T, y:T, z:T},
    Velocity{x:T, y:T, z:T},
}

let pos = CoordKind::Position::<f32>{x: 1.0, y: 2.0, z: 3.0}

このとき、明示的に型指定する場合はCoordKind::<T>::PositionではなくCoordKind::Position::<T>になる。これでちょっと驚いた。

これに対しても普通にmatchif letが使える。その際は、普通にrustc型推論をするので普通のenumと同様に使える。

let pos = CoordKind::Position::<f32>{x: 1.0, y: 2.0, z: 3.0}
match pos {
    CoordKind::Position{x, y, z} => println!("{} {} {}", x, y, z),
    CoordKind::Velocity{x, y, z} => println!("{} {} {}", x, y, z),
}

もし必要なら、CoordKind::Position::<T>{x, y, z}のように書けば良い。

match pos {
    CoordKind::Position::<f32>{x, y, z} => println!("{} {} {}", x, y, z),
    CoordKind::Velocity::<f32>{x, y, z} => println!("{} {} {}", x, y, z),
}

間違った型を指定すると以下のように教えてくれる。

error[E0308]: mismatched types
  --> src/main.rs:15:9
   |
15 |         CoordKind::Velocity::<f64>{x, y, z} => println!("{} {} {}", x, y, z),
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected f32, found f64
   |
   = note: expected type `CoordKind<f32>`
              found type `CoordKind<f64>`

この型指定方法の微妙なところは、以下のようなものを定義するとよくわかる。

enum Foo<T, U> {
    Hoge{x: T},
    Piyo{x: T},
}

これを明示的に型指定する場合、以下のようにする必要がある。

let x = Foo::Hoge::<i32, f64>{x: 42};

しかも、この場合i32はともかくf64は推論しようがないので(他の行でxFoo::Piyo<i32, f64>として使うとかしていない限り)明示的に書く必要がある。

これ、やはり以下の方が直感的だったのではないだろうか。

let x = Foo::<i32, f64>::Hoge{x: 42};

まあ、文法規則は直感的かどうかよりも面倒な縛りがたくさんあるのだろうということは想像はつくが。