所有権を持つトレイトオブジェクト

前に、トレイトオブジェクトを参照とともに使用する方法、たとえば &dyn Pet を見ました。 しかし、Box のようなスマートポインタとトレイトオブジェクトを組み合わせて使用し、 所有権を持つトレイトオブジェクト Box<dyn Pet> を作ることもできます。

// 著作権 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!("ワン、私の名前は{}です!", self.name)
    }
}

impl Pet for Cat {
    fn talk(&self) -> String {
        String::from("ニャー!")
    }
}

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!("こんにちは、あなたは誰ですか? {}", pet.talk());
    }
}

pets を割り当てた後のメモリレイアウト:

<Dog as Pet>::talk<Cat as Pet>::talkStackHeapFidoptrlives9len2capacity2data:name,4,4age5vtablevtablepets: Vec<Box<dyn Pet>>data: CatDogProgram text
  • 特定のトレイトを実装する型は、それぞれサイズが異なる場合があります。これにより、 上の例の Vec<dyn Pet> のようなものを持つことはできません。
  • dyn Pet は、Pet を実装する動的サイズ型についてコンパイラに伝える 方法です。
  • この例では、pets はスタックに割り当てられ、ベクタのデータはヒープ上に あります。2 つのベクタ要素は ファットポインタ です:
    • ファットポインタは、幅が 2 倍のポインタです。これには 2 つの構成要素が あります。実際のオブジェクトへのポインタと、その特定のオブジェクトに対する Pet 実装の virtual method table(vtable)へのポインタです。
    • Fido という名前の Dog のデータは、name フィールドと age フィールドです。Cat には lives フィールドがあります。
  • 上の例で、次の出力を比較してください:
    // 著作権 2024 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    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>>());