Viv*_*dav 11 traits vtable rust trait-objects
如果我有一个特质Foo,并且有一些实现者Bar,Baz.
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 吗?这种行为在调试和发布版本之间是否会发生变化?
让我们来看看吧。我将类似的代码放在Rust Playground 中并运行 \xe2\x80\x9cShow Assembly\xe2\x80\x9d:
\ntrait 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}\nRun Code Online (Sandbox Code Playgroud)\n在(调试模式)汇编输出中,main是:
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\nRun Code Online (Sandbox Code Playgroud)\n我们不需要能够读取 x86 程序集的每个细节,就能看到这里有三个函数调用,最后两个是我为了比较而添加的静态调用。所以,.L__unnamed_2可能与vtable有关。那是什么?
.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\nRun Code Online (Sandbox Code Playgroud)\n对我来说看起来像一个 vtable:它指的是 dropglue 和x(). u16但是对于\xe2\x80\x94没有任何作用,唯一的参考<u16 as playground::Foo>::x是main.
当然,这并不排除编译器生成了 vtable 数据,然后在到达程序集列表之前将其丢弃。但如果确实如此,那么要么这将是一个编译器性能错误,要么它的成本太低以至于不值得担心。
\n(此外,还有更多轶事证据:众所周知,Rust 编译器会为同一类型生成多个 vtable,如果它们碰巧在单独的代码生成单元中需要的话。)
\n