ディスパッチとは、呼び出す特定の関数(メソッド)を選択するメカニズムです。Rustには、静的ディスパッチと動的ディスパッチの2つのアプローチがあります。
問題の背景:
オブジェクト指向プログラミングの言語では、通常、メソッドを動的に呼び出すためにvtable(仮想テーブル)が使用されます。Rustでは、特定のトレイトを実装する型のオブジェクトへの参照であるトレイトオブジェクトに対して同様のものが実装されています。静的ディスパッチは、ジェネリクスやトレイトバウンドを使用することで実現されます。
問題:
しばしば、異なる型のオブジェクトを1つのインターフェースで扱う柔軟性と、メソッドをインライン化できる静的ディスパッチによる性能との間で選択を迫られます。不適切な選択は、過度に複雑なジェネリクスや性能の低下を引き起こします。
解決策:
静的ディスパッチは、ジェネリックパラメータを介して達成されます。この場合、コンパイラは各型のために個別のコードを生成します。動的ディスパッチは、関数が&dyn TraitまたはBox<dyn Trait>型の引数を取る場合に発生し、Rustは従来のオブジェクト指向言語のようにvtableを参照します。
コードの例:
trait Shape { fn area(&self) -> f64; } impl Shape for Circle { fn area(&self) -> f64 { 3.1415 * self.radius * self.radius } } fn print_area(shape: &dyn Shape) { // 動的ディスパッチ println!("area = {}", shape.area()); } // または静的に: fn print_area_static<S: Shape>(shape: &S) { println!("area = {}", shape.area()); }
主な特徴:
dyn Traitはvtableを使用(動的ディスパッチ)Box<dyn Sized>は作成できますか?
いいえ。dyn Traitは定義上、unsizedであり、常にBox、Arc、または参照を使用する必要がありますが、「Box<dyn Sized>」は意味がありません。Sizedトレイトはトレイトオブジェクトにはありません。
ジェネリックメソッドを持つトレイトにdyn Traitは許可されていますか?
いいえ。ジェネリックメソッドを持つオブジェクトセーフなトレイトは作成できません(混同されやすいです!)、複合型はオブジェクトセーフではありません:
trait MyTrait { fn foo<T>(&self, x: T); } let x: &dyn MyTrait = ... // コンパイルエラー!
シグネチャにSelfがあるトレイトにdyn Traitを作成できますか?
できません。メソッドがSelfを返す場合(多くの人がこのニュアンスを理解していません:オブジェクトセーフティは、シグネチャにSelfがないことを要求します;引数にはselfを使用できますが、戻り値にはできません)。
どこでもインターフェースの汎用性のためにdyn Traitを使用し、tight loop内でもジェネリクスで対処できた場合でも使用していました。
プラス:
マイナス:
静的ディスパッチが内部ロジックに使われ、dyn Traitはモジュールの境界でのみ使用されました。
プラス:
マイナス: