2
votes

I am having trouble understanding the following error:

// test.rs

struct A {
    a: u32
}

trait B { }

impl B for A { }

struct C {
    c: Vec<Box<dyn B>>,
}

fn test() {
    let a = A {a: 1};
    let a_vec = vec![Box::new(a)];
    let c = C {
        c: a_vec,
        // c: vec![Box::new(a)],
    };
}

Compile error:

mismatched types

expected trait object `dyn test::B`, found struct `test::A`

The error occurs on the line where I try to create C. What's interesting is that if I create C in the following way then it compiles alright.

let c = C {
        c: vec![Box::new(a)],
    };

Another way that would work is

let a_vec: Vec<Box<dyn B>> = vec![Box::new(a)];
let c = C {
        c: a_vec,
    };

One other experiment I did is to change the type of C.c to just Box instead of Vec<Box>, and then it compiles no matter how I initiate it.

Seems to me this might be some missing feature/bug of Rust's type inference regarding vector of trait objects? I am still very new to Rust so any idea on this is really appreciated!

1
Another variant that works is vec![Box::new(a) as _]. The error itself is due to the difference between the thin pointer (boxed concrete struct) and a fat pointer (boxed dyn trait). One is convertible to the other (provided the struct implements the trait), but sometimes the compiler needs some nudging to perform the conversion. Whether that's a deficiency in the type inference or a consequence of some deeper principle is a good question. - user4815162342

1 Answers

0
votes

This is all expected behaviour. A Box<A> can be coerced into a Box<dyn B> – this is called an unsized coercion, and it happens implicitly whenever the compiler knows that the target type is Box<dyn B>. However, when just writing

let a_vec = vec![Box::new(a)];

the compiler doesn't know the item type of the vector, so it infers it from the expression on the right-hand side, meaning that a_vec ends up with the type Vec<Box<A>>.

There is no unsized coercion from Vec<Box<A>> to Vec<Box<dyn B>>. Converting Box<A> to Box<dyn B> simply means converting a pointer to a fat pointer on the stack, which is a cheap operation. Converting vectors of these elements is very different – it would require reallocating the whole vector and unsizing each element, which is a rather expensive operation, so it should never happen implicitly.

In all the versions that actually compile, the vector is created as a Vec<Box<dyn B>> right from the get-go, since you tell the compiler that's the type you want.