Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does Box<dyn Trait> deconstruct itself?

Tags:

rust

Since it doesn't know the concrete type of the data, it only contains a vtpr of dyn Trait, How does it drop itself when it goes out of scope? Does every virtual table in Rust contains a drop method implementation?

like image 814
炸鱼薯条德里克 Avatar asked Jun 05 '20 03:06

炸鱼薯条德里克


People also ask

What is box DYN in Rust?

The dyn keyword is used when declaring a trait object: The size of a trait is not known at compile-time; therefore, traits have to be wrapped inside a Box when creating a vector trait object.

What does Dyn do in Rust?

The dyn keyword is used to highlight that calls to methods on the associated Trait are dynamically dispatched. To use the trait this way, it must be 'object safe'. Unlike generic parameters or impl Trait , the compiler does not know the concrete type that is being passed. That is, the type has been erased.

What is a trait object rust?

A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits. Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.

Why can't I use the clone method on a box?

The reason why the code above fails is that even though Box holds the trait implementation on the heap, using clone on a Box copies the object rather than a fat pointer to it. That's why the compiler says that our objects must be Sized if we want to use the clone method.

What is a trait object in rust?

A Trait Object represents a pointer to some concrete type that implements a Trait (think interface if you are unfamiliar with the term Trait ). Trait Objects are Dynamically Sized Types, and because Rust needs to know everything at compile time about the size of the types it works with, Trait Objects are handled a bit differently.

What is the difference between Box<T> and RC<T >?

The goal is to highlight some of the differences between Box<T> ( Heap allocated object) and Rc<T> ( Reference Counting pointer ), which are both very important container types you should master early on. Box<T> is a container type designed to allocate and "hold" an object on the heap.


Video Answer


1 Answers

When the concrete type the original Box contained is unsized into a trait object, the Drop implementation for the type goes into the vtable. A pointer (Any pointer-like thing in Rust. IE, a reference, Box, raw pointer, etc.) whose pointee is a trait object is laid out as follows in memory*:

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

The ptr field in my example points to the actual data. We could say that's the original Box.

The vtable field in my example points to a static vtable. Say we have the following Foo trait:

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

Our vtable will look as follows*:

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

We see there, that the drop is there. Along with it, there're size and align fields which allow owning types to deallocate enough memory. Or re-allocate enough memory.

Here's an example program which crudely extracts the size of a struct from within a pointer to a trait object:

#![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);
}

Playground

(Currently) prints:

size: 8, align: 8
Value: 20

However this may very well change in the future so I'm leaving this timestamp here for someone who reads this in a future where the behaviour has been changed. June 5, 2020.


*: The layout of trait objects, and especially their vtables is NOT guaranteed, so do not rely in actual code.

like image 184
Optimistic Peach Avatar answered Oct 14 '22 01:10

Optimistic Peach