トレイトオブジェクト

トレイトオブジェクトは異なる型の値をひとつのコレクションにまとめることを可能にします:

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を割り当てた後のメモリレイアウト:

<Dog as Pet>::talk<Cat as Pet>::talkStackHeappetsFidoptrlen2capacity2dataname,4,4age5vtabledatalives9vtable
This slide should take about 10 minutes.
  • 同じトレイトを実装する型であってもそのサイズは異なることがあります。そのため、上の例でVecと書くことはできません。
  • dyn Pet はコンパイラに、この型がPetトレイトを実装する動的なサイズの型であることを伝えます。
  • 上の例では pets はスタックに確保され、ベクターのデータはヒープ上にあります。二つのベクターの要素は ファットポインタ です:
    • ファットポインタはdouble-widthポインタです。これは二つの要素からなります:実際のオブジェクトへのポインタと、そのオブジェクトのPetの実装のための仮想関数テーブル (vtable)です。
    • “Fido“と名付けられたDogのデータはnameage のフィールドに対応します。(訳注: “Fido“とはよくある犬の愛称で、日本語でいう「ポチ」のような名前です。)例のCatにはlives フィールドがあります。(訳注: ここでCatlivesというフィールドを持ち、9で初期化しているのは“A cat has nine lives” —猫は9つの命を持つ—ということわざに由来します。)
  • 上の例において、下のコードによる出力結果を比べてみましょう:
    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>>());