TL;DR

Rust で、あるトレイトを実装した型の値と、その型のメソッドを実行時に引くための表(vtable)の両方を指すもの。&dyn Trait や Box<dyn Trait> の形で使い、動的ディスパッチを実現する。

なぜ必要か

ジェネリクスでは、型パラメータは 1 つの具体的な型に決まる。トレイトオブジェクトを使うと、同じトレイトを実装した異なる型の値を、1 つのコレクションにまとめて扱える。

仕組み

The Rust Programming Language によると、トレイトオブジェクトは参照や Box<T> のようなポインタに dyn キーワードとトレイト名を付けて作る。ジェネリクスはコンパイル時に具体的な型ごとのコードを生成し、呼ぶメソッドがコンパイル時に決まる(静的ディスパッチ)。トレイトオブジェクトでは、実行時にトレイトオブジェクト内のポインタをたどって呼ぶメソッドを決める。

Effective Rust によると、トレイトオブジェクトは値へのポインタと vtable へのポインタからなるファットポインタである。vtable には、メソッドの関数ポインタに加え、型のサイズやアラインメント、drop 関数へのポインタも入っている。

例

pub trait Draw {
    fn draw(&self);
}
 
pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

Screen は、Draw を実装していればボタンでもセレクトボックスでも components に入れられる。

注意点

実行時にメソッドを引くコストがかかり、インライン化などの最適化も効かなくなる。トレイトオブジェクトにできるのは、トレイトオブジェクト安全性の条件を満たすトレイトだけである。また、具体的な型の情報は失われるので、Box<dyn Error> からは元のエラー型のメソッドを呼べない。

関連

出典