Box<dyn Trait> 如何自我解构?

炸鱼薯*_*德里克 5 rust

由于它不知道数据的具体类型,它只包含一个 dyn Trait 的 vtpr,当它超出范围时它是如何丢弃自己的?Rust 中的每个虚拟表都包含一个 drop 方法实现吗?

Opt*_*ach 5

当原始Box包含的具体类型未调整为 trait 对象时,Drop该类型的实现将进入 vtable。一个指针(Rust 中的任何类似指针的东西。IE,一个引用,,Box原始指针等),其指针是一个 trait 对象,在内存中布局如下:

struct FooTraitDynPointer {
    ptr: *[const/mut] (),
    vtable: &'static VTableImplForFooTrait
}
Run Code Online (Sandbox Code Playgroud)

ptr我的示例中的字段指向实际数据。我们可以说这是原始的Box.

vtable我的示例中的字段指向静态 vtable。假设我们有以下Foo特征:

trait Foo {
    fn bar(&self) -> usize;
}
Run Code Online (Sandbox Code Playgroud)

我们的 vtable 将如下所示*:

struct VTableImplForFooTrait {
    dropper: unsafe fn(*mut ()),
    size: usize,
    align: usize,
    bar: unsafe fn(*const ()) -> usize,
}
Run Code Online (Sandbox Code Playgroud)

我们看到那里,drop 就是那里。除此之外,还有 size 和 align 字段,它们允许拥​​有的类型释放足够的内存。或者重新分配足够的内存。

这是一个示例程序,它从指向 trait 对象的指针中粗略地提取结构的大小:

#![feature(raw)]

trait Foo {
    fn bar(&self) -> usize;
}

struct Baz {
    field: f64
}

impl Foo for Baz {
    fn bar(&self) -> usize {
        self.field as usize
    }
}

#[derive(Clone)]
struct FooVTable {
    dropper: unsafe fn(*mut ()),
    size: usize,
    align: usize,
    bar: unsafe fn(*const ()) -> usize,
}

fn main() {
    use std::{mem, raw};
    let value = Baz { field: 20.0 };

    let boxed = Box::new(value) as Box<dyn Foo>;

    let deconstructed: raw::TraitObject = unsafe { mem::transmute(boxed) };

    let vtable = deconstructed.vtable as *mut FooVTable;

    let vtable = unsafe { (*vtable).clone() };

    println!("size: {}, align: {}", vtable.size, vtable.align);

    let result = unsafe { (vtable.bar)(deconstructed.data) };

    println!("Value: {}", result);
}
Run Code Online (Sandbox Code Playgroud)

操场

(当前)打印:

size: 8, align: 8
Value: 20
Run Code Online (Sandbox Code Playgroud)

但是,这在未来很可能会发生变化,所以我将这个时间戳留在这里,供将来阅读此行为已更改的人使用。2020 年 6 月 5 日。


*: trait 对象的布局,尤其是它们的 vtables没有保证,所以不要依赖实际代码。