Closed4

Rustのdyn Traitとimpl Traitの違いについて

ゴリラ@時代はRustですゴリラ@時代はRustです

dyn Traitimpl Trait の違いについて整理する

  • 具体的な違い
  • 使い分け
  • メリット/デメリット

dyn Trait

  • いわゆる、トレイトオブジェクトのこと
    trait Reader {
        fn read(&self) -> Vec<u8>;
    }
    
    fn read_full(_: &dyn Reader) {}
    

impl Trait

  • こういうやつ
    trait Reader {
        fn read(&self) -> Vec<u8>;
    }
    
    fn read_full(_: &impl Reader) {}
    

参考文献

ゴリラ@時代はRustですゴリラ@時代はRustです

dyn Trait

  • Traitを実装しているオブジェクトとメソッドへの参照を持つFat Pointerを使って実現している
    • Fat Pointerは通常のポインタとは異なり、参照以外のデータも持つ
    • 身近の例でいうとVecはデータへの参照と長さを持っているため、Fat Pointerに当たる
  • いわゆる動的ディスパッチ
  • 自前で実装すると次のような感じになる
    use std::mem::{align_of, size_of, transmute};
    
    trait Adder {
        fn add(&self) -> isize;
    }
    
    #[derive(Debug)]
    struct Data {
        a: isize,
        b: isize,
    }
    
    fn add(data: &Data) -> isize {
        data.a + data.b
    }
    
    fn call_adder(adder: &dyn Adder) {
        dbg!(adder.add());
    }
    
    // Cと同様にフィールドの順にメモリを確保する
    #[repr(C)]
    struct TraitObject<'a, T> {
        data: &'a mut T, // Traitを実装したデータへの参照
        vtable: *const usize, // vtable の生ポインタ(ただの数値)
    }
    
    fn main() {
        let mut data = Data { a: 1, b: 2 };
        // vtableの情報を経由して、実際にトレイトオブジェクトのメソッドを呼び出す
        // vtableのデータはそれぞれ
        // 0番目    : Drop へのポインタ
        // 1番目    : 元のデータのバイト数
        // 2番目    : 元のデータのアライメント
        // 3番目以降: メソッドへの生ポインタ(Traitのメソッド宣言順)
        let vtable = vec![0, size_of::<Data>(), align_of::<Data>(), add as usize];
        let object = TraitObject {
            data: &mut data,
            vtable: vtable.as_ptr(),
        };
        // TraitObject から &dyn Adder へ型変換
        let adder = unsafe { transmute::<TraitObject<Data>, &dyn Adder>(object) };
        call_adder(adder);
    }
    
ゴリラ@時代はRustですゴリラ@時代はRustです

impl Trait

  • Monomorphizationで型ごとに実装が作られる
  • コンパイル時に実装が追加されるので、実行時のオーバーヘッドがない
    • dyn Traitでは動的に関数呼び出しを行うので、それと比べてという意味
  • いわゆる静的ディスパッチ
  • コンパイル前
    fn do_something<T: Foo>(x: T) {
        x.method();
    }
    
    fn main() {
        let x = 5u8;
        let y = "Hello".to_string();
    
        do_something(x);
        do_something(y);
    }
    
    コンパイル時に型ごとの実装が追加されるイメージ
    fn do_something_u8(x: u8) {
        x.method();
    }
    
    fn do_something_string(x: String) {
        x.method();
    }
    
    fn main() {
        let x = 5u8;
        let y = "Hello".to_string();
    
        do_something_u8(x);
        do_something_string(y);
    }
    

参考文献

このスクラップは2023/01/10にクローズされました