Got it! One more thing, in your example (10th floor)
// a helper-trait to do the conversion
trait AsBar {
fn as_bar(&self) -> &dyn Bar;
}
// note that Bar requires `AsBar`, this is what allows you to call `as_bar`
// from a trait object of something that requires `Bar` as a super-trait
trait Bar: AsBar {
fn bar_method(&self) {
println!("this is bar");
}
}
// no change here
trait Foo: Bar {
fn foo_method(&self) {
println!("this is foo");
}
}
in your answer,
pub struct VTableFoo {
layout: Layout,
drop_in_place: unsafe fn(*mut ()),
// methods
foo_method: fn(*mut ()),
bar_method: fn(*mut ()),
as_bar: fn(*mut ()) -> &dyn Bar,
}
I'm a little bit confused. I thought vtable for Foo will only have only one method, which means foo_method
and trait object dyn Foo
can only call foo_method
and it cannot call bar_method
.
Now it seems the vtable should include all methods from siblings. That is to say
Trait A: B, C {}
// flat relation graph
// B, C
// ^
// A
Table for trait object A should have all methods from B, C, which makes the vtable really flat (maybe large in size).
Am I right on this?
// c++: foo.cc
struct C {
virtual ~C() = default;
};
struct B : public C {
virtual ~B() = default;
};
struct A : public B {};
int main(int argc, char **argv) { return sizeof(A); }
// hierarchical relation
// C
// ^
// B
// ^
// A
run clang -cc1 -std=c++11 -fdump-record-layouts foo.cc
to check memory layout,
*** Dumping AST Record Layout
0 | struct C
0 | (C vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]
*** Dumping AST Record Layout
0 | struct B
0 | struct C (primary base)
0 | (C vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]
*** Dumping AST Record Layout
0 | struct A
0 | struct B (primary base)
0 | struct C (primary base)
0 | (C vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]
There's only one vtable for A, B, and C. But for Rust, there is a vtable implementation for each trait (e.g., A, B and C).
To me, even though Rust inheritance is not the same as C++, but the vtable layout, in the end, is the same for struct A and trait A. The only difference is that C++ vtable lives inside of each object whereas rust lives in the fat pointer, alongside data pointer.
In the long run, the number of objects is far more than the number of traits, this decision may save some space.
One more question that I have.
How does vtable check which method to be used at runtime if it has many methods in the table? Is the method offset compile-time known? E.g., if foo_method
is called, rust will remember its offset, let's say 3 at compile-time, and at runtime use that index 3 to load the method for foo?