Search code examples
rusttrait-objects

Incorrect type inference for Rust vector of trait object


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!


Solution

  • 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.