Search code examples
rusttrait-objects

How to get the v-ptr for a given Trait/Struct combination?


In Rust, a &T where T is a trait is a fat reference, which actually corresponds to raw::TraitObject:

pub struct TraitObject {
    pub data: *mut (),
    pub vtable: *mut (),
}

Using TraitObject, one can de-construct and re-construct a &T at leisure.

However, while obtaining the vtable from de-constructing a &T is easy, what if I never have the &T in the first place, but just a T and S; essentially, something along the lines of:

fn make_vptr<T: ?Sized, S>() -> *mut ();

How could I divine the v-ptr from there? Is there any intrinsic I could use?

Note: the naive implementation of creating a S (or conjuring it from thin-air) and then making a &T reference does not work; the compiler complains that T is not necessarily a trait and therefore that &T is either one pointer or two pointers in size.


Solution

  • A possibility is to use a macro to do the magic job:

    #![feature(raw)]
    
    macro_rules! make_vptr(
        ($S:ty, $T:ty) => ({
            let s: &$S = unsafe { ::std::mem::uninitialized() };
            let t: &$T = s;
            let r: ::std::raw::TraitObject = unsafe { ::std::mem::transmute(t) };
            r.vtable
        })
    );
    

    This code will not compile if T is not a trait (thanks to transmute(..) checking that &T is a fat pointer) or if T is not implemented by S (thanks to the assignment).

    Then, it can be used directly:

    use std::fmt::Display;
    
    fn main() {
        let u32_display_vtable = make_vptr!(u32, Display);
    
        let x = 42u32;
    
        let disp: &Display = unsafe {
            ::std::mem::transmute(::std::raw::TraitObject {
                data: &x as *const _ as *mut _,
                vtable: u32_display_vtable,
            })
        };
    
        println!("{}", disp);
    }