Rustでenumのタイプエイリアスでヴァリアントもエイリアスして欲しい問題のワークアラウンドとか
κeenです。何度か「Rustでenumのタイプエイリアスしたときにヴァリアントもエイリアスして欲しい」という話を聞くので自分なりにどうにか出来ないかなと考えたやつをまとめます。
モチベーション
やりたいことはこんなの。
enumにtype
で別名を付けたときに 別名::ヴァリアント名
でアクセスしたいというもの。
特にジェネリクスを使ってるときにやりたくなる。
enum Foo<V> {
Hoge,
Piyo(V),
}
type IntFoo = Foo<i64>;
// ここで `IntFoo::Piyo` とアクセスしたい。
一応RFCにもなっているのでいつかは解決される。 しかしTracking issueはOpenでありいつ解決されるかは分からない。
そこでワークアラウンドを考えてみた。
コード
要は型だけでなく名前空間もエイリアスしたのでイメージとしてはやりたいことはこうなる。
単相化する必要があるのでシンプルに use Foo::*
するのはできなくて、真心込めて関数やConstを書いていく必要がある。
enum Foo<V> {
Hoge,
Fuga { x: i64, y: i64 },
Piyo(V),
}
type IntFoo = Foo<i64>;
mod IntFoo {
use super::{Foo, IntFoo};
// Fooがpubでないので可視性を`pub(super)`にしてある。この辺は臨機応変に。
pub(super) const Hoge: IntFoo = Foo::Hoge;
pub(super) fn Piyo(v: i64) -> IntFoo {
Foo::Piyo(v)
}
}
ただし問題が2つある。1つ目はシンプルにコンパイルエラーになる点。型名とモジュール名は被ってはいけないらしい。というかモジュールって型名空間に入るんだ。
error[E0428]: the name \`IntFoo\` is defined multiple times
--> enum_typename.rs:9:1
|
7 | type IntFoo = Foo<i64>;
| ----------------------- previous definition of the type \`IntFoo\` here
8 |
9 | mod IntFoo {
| ^^^^^^^^^^ \`IntFoo\` redefined here
|
= note: \`IntFoo\` must be defined only once in the type namespace of this module
もう一つは構造体型のヴァリアントをエイリアス出来ない点。上の例でも Fuga
だけエイリアスがない。
これを回避したワークアラウンドがいくつかある。
1つ目の問題の解決策は2つある。
1つはモジュール名を変える。IntFoo_
とか。ダサいけど。
もう一つはtype IntFoo
の方を変える。モジュール内に入れてしまって名前を T
とかにする。
mod IntFoo {
use super::Foo;
pub(super) type T = Foo<i64>;
}
RustっぽくはないけどMLのモジュールっぽい使い方。 残念ながらどちらの方法もRustにヴァリアントのエイリアスが実装されたら既存コードを変更して回らないといけない。
2つめの問題はシンプルに単相化を諦めるしかなさそう。
mod IntFoo {
use super::Foo;
pub(super) const Hoge: T = Foo::Hoge;
// FooのFugaをそのまま使う
pub(super) use super::Foo::Fuga;
pub(super) fn Piyo(v: i64) -> T {
Foo::Piyo(v)
}
}
Fuga
だけ多相のままにすることであるいは諦めて全部多相のままにすれば幾分がシンプルになる。
mod IntFoo {
use super::Foo;
pub(super) use super::Foo::*;
}
これも状況に応じて。
まとめ
「Rustでenumのタイプエイリアスしたときにヴァリアントもエイリアスして欲しい」という欲求にはには言語側で対応中だが今すぐには使えない。 そこでいくつかワークアラウンドの案を挙げたがどれも言語側の対応が済むとコードの変更が必要になり中途半端。 また、実現したいことを全て出来るわけではない。