How long does a vtable live?

Hi! Say that I have a value and a trait:

trait Foo {
    fn bar(&self) -> usize {
        println!("abc");
        20
    }
}

impl Foo for usize {}

let x = 12usize;

Now, if I take a trait object reference to x, is the vtable created before (So practically embedded into the program) or during the creation of the object?

let y: &dyn Foo = &x;

Furthermore, the point of this post, when does this vtable get destroyed? For example:

{
    let y: &dyn Foo = &x;
} //Would the vtable be destroyed here or 
//Live onto here?

If that’s the case what would happen if I did something similar to the following:

struct ContainsVTable<T> {
    val: T,
    vtable: *const () //Must remain private to ensure proper initialization through `new`
}

impl<T: Foo + Sized> ContainsVTable<T> {
    fn new(x: T) -> Self {
        //Get the vtable
        let vtable = unsafe {
            std::mem::transmute::<&dyn Foo, (*const T, *const ())>(&x).1
        }; 
        Self { val: x, vtable }
    }
}

impl<T> ContainsVTable<T> {
    //Just a regular reference
    fn as_t(&self) -> &T {
        &self.val
    }
    //Compiler can't prove that T: Foo here,
    //But we can, because `new(x: T) -> Self where T: Foo`
    //And therefore `T: !Foo` is impossible without unsafe code. 
    fn as_dyn_foo(&self) -> &dyn Foo {
        unsafe {
            std::mem::transmute::<
                (&T, *const ()),
                &dyn Foo
            >((&self.val, self.vtable))
        }
    }
}

Would this invalidate the vtable because I could potentially be handing it out multiple times for a single “generated” vtable?

How would this differ from a mutable reference with a trait that could use mutable references? Would the rules change there or not?

Sorry for the needlessly complex post, but I need to know this, because I might write a library which would depend on certain rules staying true.

I’m pretty sure that the vtable lives in the binary and that trait objects contain pointers to that vtable like how string literals are pointers into the binary.

Note that tuple layouts are unstable, so you should use a repr(C) type like this one when transmuting fat pointers.

1 Like

Ah! Great insight, it would make sense to do that in the case that the compiler sees that at some point there will be a vtable generated. Then again, the VTable is tied to the type it’s hiding, and not the object, so it would not matter what object I have.

Think of vtable as like &'static str. It exists similar to the str.

To be “complicated” a Trait-Structure pair can have multiple vtables generated by the compiler. (think const vs static.) The relevance to code is that your fat pointers &dyn Trat even though pointing to the same structure may compare as different. So have to cast to any thin eg *const () if performing pointer comparison.