Rustでプログラミングをしているとオブジェクト指向のように多態性を表現したくなります
よくある動物が鳴くやつをtrait
で実現してみます
/// 鳴くトレイト
trait Bark {
fn bark(&self) -> ();
}
/// 犬
struct Dog;
impl Bark for Dog {
fn bark(&self) -> () {
println!("わんわん!");
}
}
/// 猫
struct Cat;
impl Bark for Cat {
fn bark(&self) -> () {
println!("にゃーにゃー!");
}
}
これをオブジェクト指向よろしく扱ってみます
fn main() {
let mut animal: Box<dyn Bark> = Box::new(Dog);
animal.bark(); // "わんわん!"
animal = Box::new(Cat);
animal.bark(); // "にゃーにゃー!"
}
ここで気になるのが動的ディスパッチになっていることです
Box<dyn Bark>
のところですね
トレイトオブジェクトを呼び出す際にメソッドを解決するのに実行コストがかかるというのが定説です(静的ディスパッチ比)
そういわれるとなんとなく避けたいなぁとなってしまうのが人情でしょう(?)
動的ディスパッチを避けるためにはdyn Bark
を使用しないようにしないといけません
そこで最近覚えたのがenumによるラップです
以下のようにenumを宣言します
enum Animal {
Dog(Dog),
Cat(Cat)
}
impl Bark for Animal {
fn bark(&self) -> () {
match self {
Animal::Dog(inner) => inner.bark(),
Animal::Cat(inner) => inner.bark(),
}
}
}
enum Animal
を定義してDog
やCat
と同様にBark
トレイトを実装し、
それぞれのパターンごとにbark()
を実行するわけです
これにより動的解決が回避され、静的ディスパッチになります
あとは以下のように呼び出せばよいですね
let mut animal: Animal = Animal::Dog(Dog);
animal.bark(); // "わんわん!"
animal = Animal::Cat(Cat);
animal.bark(); // "にゃーにゃー!"
これで静的ディスパッチが行えるようになりました
やはり動的ディスパッチと比べると一手間かかってしまうのが難点ですが、
パフォーマンスが大事なシステムなどでやっておくに越したことはないでしょう(?)
上記のやり方であればオブジェクト指向らしくデータと処理をまとめておけるので、
処理の見通しもよく、メンテもしやすいんじゃないでしょうか
ほかのやり方もあるかもしれないのでもっと探究していきたいですね🚀