让我们来找出答案。我在 Rust Playground 中放置了类似的代码 (链接),并运行了“显示汇编”:
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();
}
在(调试模式)汇编输出中,
main
是:
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
我们不需要能够阅读x86汇编的每个细节,就可以看到这里有三个函数调用,其中后面两个是我添加的静态调用,用于比较。因此,
.L__unnamed_2
可能与虚表有关。那是什么?
.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
看起来像是一个虚函数表:它引用了 drop glue 和 x()
的实现。但是对于 u16
没有类似的内容——唯一引用 <u16 as playground::Foo>::x
的地方是在 main
中静态分发的调用。
当然,这并不排除编译器生成了虚函数表数据,然后在汇编列表之前丢弃了它。但如果是这样,那么要么这将是一个编译器性能 bug,要么就是非常便宜,不值得担心。
(此外,作为更多的轶事证据:如果它们恰好在单独的代码生成单元中需要,Rust 编译器 已知会为同一类型生成多个虚函数表。)