dyn Trait
ジェネリクスによる静的ディスパッチのためにトレイトを使うことに加えて、Rust はトレイトオブジェクトによる型消去された動的ディスパッチのためにトレイトを使うこともサポートしています。
// Copyright 2024 Google LLC // SPDX-License-Identifier: Apache-2.0 struct Dog { name: String, age: i8, } struct Cat { lives: i8, } trait Pet { fn talk(&self) -> String; } impl Pet for Dog { fn talk(&self) -> String { format!("Woof, my name is {}!", self.name) } } impl Pet for Cat { fn talk(&self) -> String { String::from("Miau!") } } // ジェネリクスと静的ディスパッチを使用する。 fn generic(pet: &impl Pet) { println!("Hello, who are you? {}", pet.talk()); } // 型消去と動的ディスパッチを使用する。 fn dynamic(pet: &dyn Pet) { println!("Hello, who are you? {}", pet.talk()); } fn main() { let cat = Cat { lives: 9 }; let dog = Dog { name: String::from("Fido"), age: 5 }; generic(&cat); generic(&dog); dynamic(&cat); dynamic(&dog); }
-
impl Traitを含むジェネリクスは、ジェネリックが具体化されるそれぞれの異なる型ごとに、関数の特殊化されたインスタンスを作成するために単相化を使用します。これは、ジェネリック関数の内部からトレイトメソッドを呼び出す場合でも、コンパイラが完全な型情報を持っており、その型に対して使うべきトレイト実装を解決できるため、依然として静的ディスパッチが使われることを意味します。 -
dyn Traitを使う場合は、代わりに 仮想メソッドテーブル(vtable)を介した動的ディスパッチが使われます。これは、どの型のPetが渡されるかに関係なく、単一のfn dynamicが使われることを意味します。 -
dyn Traitを使う場合、トレイトオブジェクトは何らかの間接参照の背後に置かれている必要があります。この場合は参照ですが、Boxのようなスマートポインタ型を使うこともできます(これは 3 日目に実演します)。 -
実行時には、
&dyn Petは「ファットポインタ」、つまり 2 つのポインタの組として表現されます。1 つのポインタはPetを実装する具体的なオブジェクトを指し、もう 1 つはその型に対するトレイト実装の vtable を指します。&dyn Petに対してtalkメソッドを呼び出すとき、コンパイラは vtable 内のtalk用関数ポインタを調べ、その関数を呼び出します。その際、その関数にはDogまたはCatへのポインタが渡されます。これを行うために、コンパイラはPetの具体的な型を知っている必要はありません。 -
dyn Traitは「型消去されている」と見なされます。これは、具体的な型が何であるかについて、もはやコンパイル時の知識を持たないためです。