dyn 型 (トレイトオブジェクト) はそれ自体に実装を付加できる。
代表例は、impl dyn MyTrait { ... } のような固有機能の実装パターン。
または、impl SomeTrait for dyn MyTrait { ... } のようなトレイトの実装パターン。
ダウンキャスト系のメソッドを実装する場合が多い。
以下では、dyn Animal を Dog にダウンキャストしている。
use std::any::Any;
fn main() {
let animal = &(Dog {}) as &dyn Animal;
let dog = animal.downcast::<Dog>();
dog.trick();
}
// -- Animal -- //
trait Animal: Any {}
impl dyn Animal {
fn downcast<T: Animal>(&self) -> &T {
(self as &dyn Any).downcast_ref().unwrap()
}
}
// -- Dog -- //
struct Dog {}
impl Animal for Dog {}
impl Dog {
fn trick(&self) {
println!("Bow!");
}
}
オブジェクトセーフでないトレイトを動的に活用できるようにする。
以下では、PartialEq トレイトの eq メソッドにより、数値型または文字列型をとる dyn Id どうしを比較している。なお、eq は比較先に Self を用いる。そのため、PartialEq はこの手法なしでは、異なる型どうしの比較も、動的型の処理もできない (オブジェクトセーフの条件を満たせないため)。
fn main() {
let x = &1 as &dyn Id;
let y = &"1".to_string() as &dyn Id;
assert!(!x.eq(y));
}
trait Id {
fn key(&self) -> String;
fn is(&self, other: &dyn Id) -> bool {
self.key() == other.key()
}
}
impl PartialEq for dyn Id {
fn eq(&self, other: &Self) -> bool {
self.is(other)
}
}
impl Id for i32 {
fn key(&self) -> String { format!("i32: {self}") }
}
impl Id for String {
fn key(&self) -> String { format!("str: {self}") }
}
上記を応用する上で、つい忘れがちな点について。
dyn TraitName は糖衣構文であり、dyn TraitName + 'static と等価である。
dyn TraitName + '_ のように匿名ライフタイムを使えば、'static 境界が解除される。