Rust enum_dispatch の簡単な解説
Rustで複数の型が同じトレイトを実装している場合、それらを enumでまとめて扱いたいことがあります。
しかし普通のenumでは、enum自体はそのトレイトを実装していないため、そのままメソッドを呼ぶことができません。
そこで便利なのが enum_dispatch です。
これは
enumに対するトレイト実装を自動生成するクレート
です。
インストール
cargo add を使う場合は次のコマンドです。
cargo add enum_dispatch
困るケース(通常のRust)
例えば Animal トレイトがあるとします。
trait Animal {
fn speak(&self) -> &'static str;
}
2つの実装:
struct Dog;
struct Cat;
impl Animal for Dog {
fn speak(&self) -> &'static str {
"woof"
}
}
impl Animal for Cat {
fn speak(&self) -> &'static str {
"meow"
}
}
これらを enum でまとめます。
enum Pet {
Dog(Dog),
Cat(Cat),
}
ここで次のようなコードを書きたくなります。
fn say(pet: Pet) {
println!("{}", pet.speak());
}
しかしこれは コンパイルできません。
理由は
Dog → Animal
Cat → Animal
Pet → Animal ではない
からです。
そのため普通は次のようなコードを書きます。
impl Animal for Pet {
fn speak(&self) -> &'static str {
match self {
Pet::Dog(d) => d.speak(),
Pet::Cat(c) => c.speak(),
}
}
}
この match はよく出てくる 定型コードです。
enum_dispatch を使う
enum_dispatch を使うと、この match を 自動生成してくれます。
use enum_dispatch::enum_dispatch;
#[enum_dispatch]
trait Animal {
fn speak(&self) -> &'static str;
}
実装は普通に書きます。
struct Dog;
struct Cat;
impl Animal for Dog {
fn speak(&self) -> &'static str {
"woof"
}
}
impl Animal for Cat {
fn speak(&self) -> &'static str {
"meow"
}
}
そして enum に attribute を付けます。
#[enum_dispatch(Animal)]
enum Pet {
Dog(Dog),
Cat(Cat),
}
すると次のような実装が 自動生成されます(イメージ)
impl Animal for Pet {
fn speak(&self) -> &'static str {
match self {
Pet::Dog(d) => d.speak(),
Pet::Cat(c) => c.speak(),
}
}
}
使い方
これで次のコードが書けるようになります。
fn say(pet: Pet) {
println!("{}", pet.speak());
}
つまり
Dog → Animal
Cat → Animal
Pet → Animal (自動生成)
という関係になります。
まとめ
enum_dispatch は
- enum に対する trait実装を自動生成する
- 手書き
matchをなくせる -
dyn Traitを使わずにポリモーフィズムを書ける
という便利なクレートです。
「複数の実装をenumでまとめて扱う設計」ではとても相性の良いライブラリです。