让我们找出答案。我把这个类似的代码in the 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 可能与 vtable 有关。那是什么?
.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
对我来说看起来像一个 vtable:它指的是滴胶和 x() 的实现。但是对于u16 没有任何相同的作用——对<u16 as playground::Foo>::x 的唯一引用是main 中的静态调度调用。
当然,这不排除编译器生成了 vtable 数据,然后在进入汇编列表之前将其丢弃。但如果是这样,那要么是编译器性能错误,要么成本太低以至于不值得担心。
(另外,作为更多轶事证据:如果碰巧在单独的代码生成单元中需要它们,Rust 编译器是 known to generate multiple vtables for the same type。)