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>){}
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.
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:
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
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.