Trait objectのDynamic dispatch
- trait objectってのは
dyn Trait
←こういうの
なんでDynamic Dispatchが必要なの?
stdドキュメント より:
Unlike generic parameters or
impl Trait
, the compiler does not know the concrete type that is being passed. That is, the type has been erased. As such, adyn Trait
reference contains two pointers. One pointer goes to the data (e.g., an instance of a struct). Another pointer goes to a map of method call names to function pointers (known as a virtual method table or vtable).
Trait objectはGenericsと逆のことをしている。
- Trait objectは
- Traitに必要な関数へのポインタのみを型から抜き出してくる(汎化する)
- その背後にある型は無視する・忘れる(型消去)
- Genericsは、
- 実際に
T
に入れられた型のそれぞれについて実装を作成する(特化する)- それぞれについて最適化ができる
- 呼び出しは(コンパイル時に)そのコピーに仕向ける
- 当然それぞれの実装がどの型に対応しているか、コンパイルの間は覚えていないといけない
- 実際に
Trait objectについては、もちろんコンパイル時にはその型がちゃんとTraitの実装を持っているか見るのだが、メモリレイアウト上では差をつくらない(同じ形のvtableを持つ)。
The compiler doesn’t know all the types that might be used with the code that is using trait objects
↑そんなことはなくね?
図解:
Vec<T>
にはboundがついていない(T: ?Sized
もない)ので、デフォルトでT: Sized
を要求されることに注意。
型消去
また、型を「忘れる」のは意図的であることに注意する。
- Dynamic dispatchingはstaticに比べてコードが小さくなるという利点がある。あえてdynamic dispatchingを採用しているときに、型の情報を覚えていたってしょうがないので忘れている、ということ。
Hoge
へのポインタ、つまりHoge
の先頭のアドレスを持ってはいるが、その後にどんなデータが入っているかdyn
は知らない
型消去は「あえて」型を無視させるので、他の言語ではまわりくどいコードを書かざるを得ないこともあるが、Rustではそれが dyn
一発でできる。
Object Safety
さて、そうやってもとの型を忘れてしまうと困るケースがある。たとえば、図の c()
の引数や返り値が Self
のとき。本来なら Hoge
を返せばいいのだが、もう忘れてしまった。どうしようもないので、こういう関数はDynamic dispatchingの対象から外している(object safety)。
- メソッドがgenericsを使っている場合も困る。Genericsを使うとコンパイル時に実装がコピーされる。
c::<i64>::(), c::<u32>::(), c::<usize>::(), ...
- わかんない
- 別にvtableに全パターンぶち込んで呼べばよくね?
where Self: Sized
をつける理由- Sizedだとobject-safeにならない?
- Sizedは「コンパイル時にサイズがわかる」だけではなくて、「サイズが一定である」こともいう
- traitはもとの型によってサイズがバラバラになるので、それを
dyn
にしたtrait objectもDST(つまり!Sized
)にならざるを得ない Sized
にすると、(traitの場合)「このtraitはtrait objectになれませんよ」(メソッドの場合)「このメソッドはtrait objectでは使えませんよ」と宣言することと同義になる- std::marker::Sizedがわかりやすい
- 引数に
self
があるとき、そのself
はSelf: Sized
を満たさなければならない一方で、元の型がわからないからコンパイル時にサイズがわからない
- Sizedだとobject-safeにならない?
等価なコードをtrait objectとgenerics両方で書ける?
trait Foo {
fn c();
fn d();
}
impl Foo for Hoge {
fn c() { do_something(); }
fn d() { do_something_once_again(); }
}
impl Foo for Piyo {
fn c() { do_something(); }
fn d() { do_something_once_again(); }
}
let v: Vec<Box<dyn Foo>> = vec![Box::new(Hoge::new()), Box::new(Piyo::new())];
for vv in v { vv.c(); }
trait objectを使わないのなら…
hoge.c();
piyo.c();
と個別に呼ぶか、
fn c<F>(this: F) {
// Foo.c()に相当する処理
}
c(piyo);
c(hoge);
って感じにする?
Backlinks
There are no notes linking to this note.