是否为实现特征的所有类型生成 vtables?

Are vtables generated for all types that implement a trait?

如果我有特质Foo,一些实现者BarBaz

impl Foo for Bar {
}
impl Foo for Baz {
}

但是假设我只使用其中一个作为特征对象,

let bar = Bar {..};
let foo: &dyn Foo = &bar;

那么我的二进制文件还会有两者的 vtable 吗? 此行为在调试版本和发布版本之间是否会发生变化?

让我们一探究竟。我把这个类似的代码 in the Rust Playground 和 运行 “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();
}

在(调试模式)汇编输出中,main是:

playground::main:
    subq    , %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    , %rsp
    retq

我们不需要读懂x86汇编的每一个细节就可以看出这里有三个函数调用,最后两个是我为了对比添加的静态调用。所以,.L__unnamed_2 可能与虚表有关。那是什么?

.L__unnamed_2:
    .quad   core::ptr::drop_in_place<u8>
    .asciz  "[=12=]1[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]1[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0"
    .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 如果它们恰好在单独的代码生成单元中需要。)