是否为所有实现特征的类型生成虚函数表?

Viv*_*dav 11 traits vtable rust trait-objects

如果我有一个特质Foo,并且有一些实现者BarBaz.

impl Foo for Bar {
}
Run Code Online (Sandbox Code Playgroud)
impl Foo for Baz {
}
Run Code Online (Sandbox Code Playgroud)

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

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

Run Code Online (Sandbox Code Playgroud)

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

Kev*_*eid 8

让我们来看看吧。我将类似的代码放在Rust Playground 中并运行 \xe2\x80\x9cShow Assembly\xe2\x80\x9d:

\n
trait Foo {\n    fn x(&self);\n}\nimpl Foo for u8 {\n    fn x(&self) {\n        dbg!("xu8");\n    }\n}\nimpl Foo for u16 {\n    fn x(&self) {\n        dbg!("xu16");\n    }\n}\n\nfn main() {\n    let foo: &dyn Foo = &123_u8;\n    foo.x();\n    123_u8.x();\n    123_u16.x();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
playground::main:\n    subq    $24, %rsp\n    leaq    .L__unnamed_12(%rip), %rax\n    movq    %rax, 8(%rsp)\n    leaq    .L__unnamed_2(%rip), %rax\n    movq    %rax, 16(%rsp)\n    leaq    .L__unnamed_12(%rip), %rdi\n    callq   *.L__unnamed_2+24(%rip)\n    leaq    .L__unnamed_12(%rip), %rdi\n    callq   <u8 as playground::Foo>::x\n    leaq    .L__unnamed_13(%rip), %rdi\n    callq   <u16 as playground::Foo>::x\n    addq    $24, %rsp\n    retq\n
Run Code Online (Sandbox Code Playgroud)\n

我们不需要能够读取 x86 程序集的每个细节,就能看到这里有三个函数调用,最后两个是我为了比较而添加的静态调用。所以,.L__unnamed_2可能与vtable有关。那是什么?

\n
.L__unnamed_2:\n    .quad   core::ptr::drop_in_place<u8>\n    .asciz  "\\001\\000\\000\\000\\000\\000\\000\\000\\001\\000\\000\\000\\000\\000\\000"\n    .quad   <u8 as playground::Foo>::x\n
Run Code Online (Sandbox Code Playgroud)\n

对我来说看起来像一个 vtable:它指的是 dropglue 和x(). u16但是对于\xe2\x80\x94没有任何作用,唯一的参考<u16 as playground::Foo>::xmain.

\n

当然,这并不排除编译器生成了 vtable 数据,然后在到达程序集列表之前将其丢弃。但如果确实如此,那么要么这将是一个编译器性能错误,要么它的成本太低以至于不值得担心。

\n

(此外,还有更多轶事证据:众所周知,Rust 编译器会为同一类型生成多个 vtable,如果它们碰巧在单独的代码生成单元中需要的话。)

\n