所有されたトレイトオブジェクト
We previously saw how trait objects can be used with references, e.g &dyn Pet
. However, we can also use trait objects with smart pointers like Box
to create an owned trait object: Box<dyn Pet>
.
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 main() { let pets: Vec<Box<dyn Pet>> = vec![ Box::new(Cat { lives: 9 }), Box::new(Dog { name: String::from("Fido"), age: 5 }), ]; for pet in pets { println!("Hello, who are you? {}", pet.talk()); } }
pets
を割り当てた後のメモリレイアウト:
This slide should take about 10 minutes.
- 同じトレイトを実装する型であってもそのサイズは異なることがあります。そのため、上の例でVec
と書くことはできません。 dyn Pet
はコンパイラに、この型がPet
トレイトを実装する動的なサイズの型であることを伝えます。- 上の例では
pets
はスタックに確保され、ベクターのデータはヒープ上にあります。二つのベクターの要素は ファットポインタ です:- ファットポインタはdouble-widthポインタです。これは二つの要素からなります:実際のオブジェクトへのポインタと、そのオブジェクトの
Pet
の実装のための仮想関数テーブル (vtable)です。 - "Fido"と名付けられた
Dog
のデータはname
とage
のフィールドに対応します。(訳注: "Fido"とはよくある犬の愛称で、日本語でいう「ポチ」のような名前です。)例のCat
にはlives
フィールドがあります。(訳注: ここでCat
がlives
というフィールドを持ち、9で初期化しているのは"A cat has nine lives" —猫は9つの命を持つ—ということわざに由来します。)
- ファットポインタはdouble-widthポインタです。これは二つの要素からなります:実際のオブジェクトへのポインタと、そのオブジェクトの
- 上の例において、下のコードによる出力結果を比べてみましょう:
println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>()); println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>()); println!("{}", std::mem::size_of::<&dyn Pet>()); println!("{}", std::mem::size_of::<Box<dyn Pet>>());