Cannot extend a vec of trait objects

I have a Vec containing boxed trait objects which I’d like to extend by the result of an iterator. Performing an loop-and-push approach works fine but using the extend-method fails with:

error[E0271]: type mismatch resolving `<std::iter::Map<std::ops::Range<usize>, [closure@src/main.rs:7:47: 7:72]> as std::iter::IntoIterator>::Item == std::boxed::Box<dyn MyTrait>`
  --> src/main.rs:13:9
   |
13 |     vec.extend(new_elements);
   |         ^^^^^^ expected struct `MyStruct`, found trait MyTrait
   |
   = note: expected type `std::boxed::Box<MyStruct>`
              found type `std::boxed::Box<dyn MyTrait>`

This is my code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fa8d7cd89a62760c681bc2e2aa5f6f2e

struct MyStruct {}
trait MyTrait {}
impl MyTrait for MyStruct {}

fn main() {
    let mut vec: Vec<Box<MyTrait>> = vec![];
    let mut new_elements = (0..16_usize).map( |_| Box::new(MyStruct {}));
    
    // works
    vec.push(new_elements.next().unwrap());
    
    // doesn't work
    vec.extend(new_elements);
    
    // works (std implementation of Vec::extend)
    while let Some(element) = new_elements.next() {
        let len = vec.len();
        if len == vec.capacity() {
            let (lower, _) = new_elements.size_hint();
            vec.reserve(lower.saturating_add(1));
        }
        unsafe {
            std::ptr::write(vec.get_unchecked_mut(len), element);
            // NB can't overflow since we would have had to alloc the address space
            vec.set_len(len + 1);
        }
    }

}

I find the error message confusing - I expected the expected and found types to be swapped.

Do I have to provide some type hints? Can I add a map to cast the type into a trait object?

Like you mention map is one fix. .map(|b| b as _)
Alternate is to start with the trait object coming from the iterator. Box::new(MyStruct {}) as _
Type inference works out the _.

Box<Struct> and Box<dyn Trait> are different types (even of different size) in most cases you have to be explicit in converting.

2 Likes

The problem comes from the closure |_| Box::new(My Struct {}) having a signature of type _ -> Box<MyStruct>.

You can either add the as _ in the closure body as @jonh suggested (the return type then becomes something that can be coerced into from a Box<MyStruct> (e.g., a Box<dyn MyTrait>).

Or you can annotate the signature of the closure:

let mut new_elements =
    (0 .. 16)
        .map(|_| -> Box<dyn MyTrait> {
            Box::new(MyStruct {}) // as Box<dyn MyTrait> /* implicit coercion if MyStruct : MyTrait */
        })
;

Aside

Using trait objects without the dyn keyword is ill-advised; I suggest you add the
#![deny(bare_trait_objects)]
lint at the beginning of your code.

5 Likes

Thank you both for all of your input!

I initially tried to cast the inner type which failed because of the missing sizedness

let mut new_elements = (0..16).map(|_| Box::new(MyStruct {} as MyTrait));

@Yandros Thanks for the dyn hint - I expected the compiler to be picky by default. Will add a --deny bare_trait_objects to the command line in my IDE to make this a global default.

RUSTFLAGS="-D bare_trait_objects" environment variable should do it.

3 Likes