Cast &Box<T: MyTrait> to &Box<dyn MyTrait>

I'm wondering how to make the following work assuming I can't change function signatures. This fails to compile with &Outer defined as &Box, &Rc, &Arc, &&, etc. but works if I employ my own type &A. I tried enabling feature(arbitrary_self_types) to see if this behavior is related, but that didn't seem to make a difference.

struct A<T: ?Sized>(T);
struct B;

// works
// type Outer<T> = A<T>;

// does not work
type Outer<T> = Box<T>;

trait MyTrait {}
impl MyTrait for B {}

fn foo(x: &Outer<B>) {
    bar(x);
}

fn bar(x: &Outer<dyn MyTrait>){}

tl;dr It's not possible.

Even if T: MyTrait, the Box<T> and the Box<dyn MyTrait> are different types. They even have different sizes. Box<T> consists of single pointer, while Box<dyn MyTrait> consists of two pointers - one for the data and one for the trait methods vtable.

Converting Box<T> into Box<dyn MyTrait> is similar to convert u32 into u64. It's not possible to convert &u32 into &u64. To have &u64 someone else should hold that 8byte sized type, not just the 4byte sized integer.

1 Like

I don't follow. This does not explain why it works for A<T> as given in the provided example (see the commented lines). Also note that Box<T: ?Sized> so T is not required to be Sized. Converting a Box<T: SomeTrait> to Box<dyn SomeTrait> is very common. Indeed, if you change the signatures to the more standard fn foo(x: Box<B>) and fn bar(x: Box<dyn MyTrait>) it works fine.

There seems to be some interaction with the fact that both & and Box act as pointer types.

Both &T and Box<T> must point to a block of memory that’s already laid out as a T. Unsized types like dyn MyTrait are special in that some information is stored inside the pointer to them so Box<T> and Box<dyn MyTrait> can both point to the same block of memory but behave differently.

To do this, the pointer value of Box<B> is different from the pointer value of Box<dyn MyTrait> even when they’re pointing to the same memory. So, if you have a &Box<B>, you can’t turn it into an &Box<dyn MyTrait> because that sort of conversion would require both Box<B> and Box<dyn MyTrait> to have identical memory representations.


In the case of A<T>, the value of T is stored inline in the structure, so the actual memory contents of A<B> and A<dyn MyTrait> are the same: the difference between them only appears in the representation of a pointer to A.

Also, back to a more practical response, wanting to have a &Box<_> input to a function is an antipattern (similar to taking a &String or a &Vec, for that matter): the outer &'_ shared borrow with a limited lifetime is already limiting the usability of the received object, so, within bar, directly receiving a & _ instead will not change what can be done with the parameter:

- fn bar(x: &Outer<dyn MyTrait>){}
+ fn bar(x: &      dyn MyTrait ){}

At the call-site, this may require to perform some explicit dereferencing:

  fn foo(x: &Outer<B>) {
-     bar(x)
+     bar(&**x)
  }

But you will now see that the code compiles, since, by flattening an unnecessary indirection, we no longer have the problem of having to fatten a pointer in pointee position (which, as mentioned by the previous posts, is not possible), and instead we have to fatten a pointer by value, which is perfectly doable :slightly_smiling_face:

3 Likes

Makes sense. Thanks for the help everyone. I didn't fully appreciate that a Box<dyn Trait> does not imply that the dyn Trait actually sits on the heap but rather that, coercing from a Box<T: Trait>, a new heap data pointer is created on the stack and a new vtable pointer is placed next to it on the stack. Reading back through the first response, this is clearly what @Hyeonu was saying.

1 Like