`dyn Trait` inference seems to only work on one layer of referencing

#1

I have a situation where I need to fulfill similar requirements to this example, but that doesn’t work, even though trait object type inferencing works when I have a single layer like this one with a raw pointer or this one with a box. Is this a known issue? I know a workaround would be this, but personally I find this rather ugly and would like to know if the compiler can sort it out itself.

1 Like

#2

How’s this instead, with a bit of explicitness around coercion to a trait object.

1 Like

#3

Wow, I’d forgotten that you could cast to _, thanks so much! But either way, is this a bug, or is this intentional?

0 Likes

#4

It’s not a bug. Your first try does not work because it attempts to coerce a &mut Box<usize> into a &mut Box<dyn Foo>, which is not possible because Box<usize> and Box<dyn Foo> have different sizes and structure: Box<usize> is one pointer, but Box<dyn Foo> is two pointers, one for the data and one for the vtable (implementation) of Foo for usize.

One way to understand trait objects (and pointers in general) is with box and arrow diagrams, so have some ASCII art.

Here’s Box<usize>:

 data
[0x8358]                                                    [..., 118, ...]
      \-----------------------------------------------------------^

Here’s Box<dyn Foo>:

 data    vtable
[0x8358, 0x4754]                      [(impl Foo for usize), ..., 118, ...]
      \       \------------------------^                          ^
       \---------------------------------------------------------/

Here’s Box<Box<usize>> (&mut Box<usize> looks the same):

 data                                                data
[0x43ac]                                            [0x8358, ..., 118, ...]
      \----------------------------------------------^    \-------^

Here’s Box<Box<dyn Foo>>:

 data             data    vtable
[0x30f8]         [0x8358, 0x4754, ..., (impl Foo for usize), ..., 118, ...]
      \-----------^    \       \-------^                          ^
                        \----------------------------------------/

To coerce a Box<usize> into a Box<dyn Foo>, all the compiler does is copy the data pointer and insert a pointer to the implementation. But you can’t coerce a Box<Box<usize>> the same way because there’s nowhere to put the vtable pointer: it won’t fit in the one-pointer-wide space that was made for a Box<usize>. So you have to first cast the Box<usize> to a Box<dyn Foo> so that when you box it up a second time it will hold both pointers from the start.

2 Likes

#5

Ah, that makes for an excellent explanation, thanks so much!

0 Likes

#6

I do wonder, however, whether the aforementioned code using as _ to trigger coercion explicitly should not be needed:

fn takes_double_pointer_foo<T>(_: *mut Box<dyn Foo<Bar = T>>) {}

fn calls_other_fn<T: Foo + 'static>(a_foo: T) {
    takes_double_pointer_foo(Box::leak(Box::new(Box::new(a_foo) as _)));
}

Inference is clearly able to figure out the target type on its own, but we do need the as to trigger coercion to take place for the rest of the mechanics to kick in.

2 Likes