Closed4
Rustのdyn Traitとimpl Traitの違いについて
dyn Trait
と impl 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) {}
参考文献
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); }
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にクローズされました