bigywater.tech
Rust · 115

Trait Object 的胖指针

“trait object 是胖指针”这句话到底指什么?这篇把胖指针拆开,看地址与元数据,再验证 vtable 与类型的对应关系、上转型、指针比较、size_of_val、Drop 和 Any 向下转型都是怎么靠 vtable 实现的。

16 字节
&dyn Trait、&[T]、&str 的大小(&T 是 8)
6 字节
&str "héllo" 的长度元数据(5 个字符)
共享
同一具体类型的所有 &dyn 共享同一个 vtable

胖指针的元数据

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    &u8 8  &[u8] 16  &str 16  &dyn Debug 16  *const dyn Debug 16  Box<dyn Debug> 16
    &v[1..4] 拆开:数据地址 = v 的地址 + 1: true  长度元数据 = 3
    &str 的元数据是字节数,不是字符数:"héllo" 长度 6 字节(5 个字符)
    &dyn Debug 转成 *const (): 8 字节数据地址 = 0x10236b3c0,vtable 信息丢掉了

trait object 与动态分发的基础见Trait Object 与动态分发(那篇里已经读过 vtable 的布局)。

同一类型共享 vtable

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    两个 A:vtable 相同 true  A 与 B:vtable 相同 false  数据地址不同 true
    上转型 &dyn Sub → &dyn Super:数据地址不变 true  vtable 指针变了 false(这个例子里没变)  通过 Super 调用 name() = A

比较 dyn 指针

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    同一个对象两个 &dyn T:ptr::eq(a, b) = true
    按数据地址比较(转成 *const ()):true

运行时才知道的信息

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      cat        size_of_val =   0  align_of_val = 1
      elephant   size_of_val = 100  align_of_val = 1
      tiger      size_of_val =  16  align_of_val = 8
      Vec<Box<dyn Animal>> 离开作用域:
      Noisy(tiger) 的 Drop 通过 vtable 被调用

dyn Any

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
      i32 = 5
      &str = str
      String = owned
      未知类型,type_id 与 f64 相同吗: true

切片的胖指针见Slice 的胖指针。

分步演示

Trait Object 胖指针
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。transmute 拆胖指针依赖当前布局(元数据是第二个机器字),不是稳定保证
    • 没有验证:多个超 trait 时上转型是否换 vtable、不同 crate 里 vtable 被复制后 ptr::eq 的表现、dyn Trait + Send 与 dyn Trait 的 vtable 是否相同、vtable 内存布局(见 Trait Object 一篇)
    京ICP备2026067502号-1京公网安备11010502063487号