If I have a trait Foo
, and some implementors Bar
, Baz
.
impl Foo for Bar {
}
impl Foo for Baz {
}
But say I only use one of them ever as a trait object,
let bar = Bar {..};
let foo: &dyn Foo = &bar;
Then will my binary still have vtable for both? Does this behaviour change between debug and release builds?
Vtables in Rust Like C++, dynamic dispatch is achieved in Rust though a table of function pointers (described here in the rust docs).
Dynamic dispatch. Rust provides dynamic dispatch through a feature called 'trait objects'. Trait objects, like &Foo or Box<Foo> , are normal values that store a value of any type that implements the given trait, where the precise type can only be known at runtime.
A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.
Let's find out. I put this similar code in the Rust Playground and ran “Show Assembly”:
trait Foo {
fn x(&self);
}
impl Foo for u8 {
fn x(&self) {
dbg!("xu8");
}
}
impl Foo for u16 {
fn x(&self) {
dbg!("xu16");
}
}
fn main() {
let foo: &dyn Foo = &123_u8;
foo.x();
123_u8.x();
123_u16.x();
}
In the (debug mode) assembly output, main
is:
playground::main:
subq $24, %rsp
leaq .L__unnamed_12(%rip), %rax
movq %rax, 8(%rsp)
leaq .L__unnamed_2(%rip), %rax
movq %rax, 16(%rsp)
leaq .L__unnamed_12(%rip), %rdi
callq *.L__unnamed_2+24(%rip)
leaq .L__unnamed_12(%rip), %rdi
callq <u8 as playground::Foo>::x
leaq .L__unnamed_13(%rip), %rdi
callq <u16 as playground::Foo>::x
addq $24, %rsp
retq
We don't need to be able to read every detail of x86 assembly to see that there are three function calls here, the last two of which are the static calls I added for comparison. So, .L__unnamed_2
probably has something to do with the vtable. What's that?
.L__unnamed_2:
.quad core::ptr::drop_in_place<u8>
.asciz "\001\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
.quad <u8 as playground::Foo>::x
Looks like a vtable to me: it's referring to drop glue and to an implementation of x()
. But there is nothing that does the same for u16
— the only reference to <u16 as playground::Foo>::x
is the statically dispatched call in main
.
Of course, this doesn't rule out that the compiler generated the vtable data, then threw it out before getting to the assembly listing. But if it did, then either that would be a compiler performance bug, or it would be so cheap as to not be worth worrying about.
(Also, as more anecdotal evidence: the Rust compiler is known to generate multiple vtables for the same type if they happen to be needed in separate codegen units.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With