In this code, I take a vector, create a struct instance, and add it to the vector boxed:

trait T {}

struct X {}
impl T for X {}

fn add_inst(vec: &mut Vec<Box<T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    // Ugly, unsafe hack I made
    unsafe { std::mem::transmute(&**vec.last().unwrap()) }
}

Obviously, it uses mem::transmute, which makes me feel it's not the right way to do this. Is this ugly hack the only way to do it?

Additionally, while this compiles in Rust 1.32, it fails in Rust 1.34:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/lib.rs:10:14
   |
10 |     unsafe { std::mem::transmute(&**vec.last().unwrap()) }
   |              ^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: `&dyn T` (128 bits)
   = note: target type: `&X` (64 bits)

2 Answers

3
rodrigo On

I think that this code is safe:

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    let b = Box::new(x);
    let ptr = &*b as *const X;
    vec.push(b);
    unsafe { &*ptr }
}

The trick is to save a raw pointer to *const X before converting it to a Box<dyn T>. Then you can convert it back to a reference before returning it from the function. It is safe because a boxed value is never moved, (unless it it moved out of the Box, of course), so ptr survives the cast of b into Box<dyn T>.

3
Shepmaster On

Your "ugly hack" is actually completely incorrect and unsafe. You were unlucky that Rust 1.32 doesn't report the error, but thankfully Rust 1.34 does.

When you store a boxed value, you create a thin pointer. This takes up the platform-native size of an integer (e.g. 32-bit on 32-bit x86, 64-bit on 64-bit x86, etc.):

+----------+
| pointer  |
| (0x1000) |
+----------+

When you store a boxed trait object, you create a fat pointer. This contains the same pointer to the data and a reference to the vtable. This pointer is two native integers in size:

+----------+----------+
| pointer  | vtable   |
| (0x1000) | (0xBEEF) |
+----------+----------+

By attempting to perform a transmute from the trait object to the reference, you are losing one of those pointers, but it's not defined which one. There's no guarantee which comes first: the data pointer or the vtable.

One solution would use std::raw::TraitObject, but this is unstable because the layout of fat pointers is still up in the air.

The solution I would recommend, which requires no unsafe code, is to use Any:

use std::any::Any;

trait T: Any {}

struct X {}
impl T for X {}

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    let l = vec.last().unwrap();
    Any::downcast_ref(l).unwrap()
}

If you couldn't / don't want to use Any, I've been told that casting a trait object pointer to a pointer to a concrete type will only keep the data pointer. Unfortunately, I cannot find an official reference for this, which means I can't fully vouch for this code, although it empirically works:

fn add_inst(vec: &mut Vec<Box<dyn T>>) -> &X {
    let x = X {};
    vec.push(Box::new(x));
    let last: &dyn T = &**vec.last().unwrap();

    // I copied this code from Stack Overflow without reading
    // it and it may not actually be safe.
    unsafe {
        let trait_obj_ptr = last as *const dyn T;
        let value_ptr = trait_obj_ptr as *const X;
        &*value_ptr
    }
}

See also: