Returning a Vector of Box of a Trait

I am currently trying to return a Vec<Box<dyn SomeTrait>> and having some difficulty with that.

Here is a test function to illustrate my problem:

// Definitions
//======================
pub trait SomeTrait {};

pub struct SomeStruct {};

impl SomeTrait for SomeStruct {};

impl SomeStruct {
  fn new() -> Self {
        SomeStruct {}
 }
};
//======================

//Does not work - error[E0308]: mismatched types
// expected `Vec<Box<(dyn SomeTrait + 'static)>>` because of return type
fn test() -> Vec<Box<dyn SomeTrait>> {
    let v : Vec<Box<SomeStruct>> = Vec::new(); 
    v
}
//Works
fn test() -> Vec<Box<dyn SomeTrait>> {
    vec![Box::new(SomeStruct::new())]
}

What is the difference between them? Why does one work, but the other doesn't?

The unsizing coercion from Box<SomeStruct> to Box<dyn SomeTrait> changes the layout of Box<SomeStruct> and thus can't happen once it's already in the Vec.

If you have to have a Vec<Box<SomeStruct>> or Vec<SomeStruct> for awhile and turn it into a Vec<Box<dyn SomeTrait>> later, you'll need something like

    // Vec<Box<SomeStruct>>
    v.into_iter().map(|s| s as _).collect()
    // Vec<SomeStruct>
    v.into_iter().map(|s| Box::new(s) as _).collect()

...but better to just start with and keep the Vec<Box<dyn SomeTrait>> if possible.

2 Likes

I see, thanks! That is interesting. Could you shed some light on the unsizing coercion? I am not familiar with that.

Sure. When you coerce from SomeStruct to dyn SomeTrait, the base type (SomeStruct) is erased. The same is true when you coerce from SomeOtherStruct to dyn SomeTrait.

So dyn SomeTrait might have a different size depending on the erased base type. We say it's a dynamically sized type (DST); we also say that it is not Sized (doesn't implement the Sized trait, which means you have a statically known size), or that it is unsized. The coercion is a form of unsized coercion.

Because they are unsized, you generally see dyn SomeTrait behind some kind of pointer or reference, like &dyn SomeTrait. The way it works is that these pointers / references are wide -- instead of being one pointer that points to the value, they are two pointers: one to the value, and another to a vtable for the trait. The vtable contains things like the size of the base type and pointers to all the methods of the trait.

Internally, Box<T> acts like an owning *mut T to the value of the T. If T happens to be unsized, then the *mut T is wide, just like above.

The main non-dyn DST in Rust is the slice, [T]. In the case of slices, the wide pointers / references include a usize recording the length of the slice (the number of elements) instead of a vtable. There is also limited support for custom DSTs which are wrappers over the built-in DSTs. (More robust custom-DST support may be added eventually).

There are other DSTs you're familiar with which are basically typed wrappers around a slice: str, OsStr, and so on.

2 Likes

Thank you! That was very informative.

Hi,
Sorry for asking again, but what does as _ do?

Without it, the compiler thinks that you want

.map(|s| s)
//   ^^^^^ closure Fn(Box<SomeStruct>) -> Box<SomeStruct>

because there are no generics here, or

.map(|s| Box::new(s))
//   ^^^^^^^^^^^^^^^ closure Fn(SomeStruct) -> Box<SomeStruct>

because while there is a generic with Box::new, it has to be a Box::<SomeStruct>::new here.

The as _ let's the compiler know that "hmm, they want to cast the Box<SomeStruct> to some type I'm supposed to infer (_)", and it can't tell what that is without looking at the wider context to see you need it to be a Box<dyn SomeTrait>.

You could spell it out with as Box<dyn SomeTrait>, but I'm lazy :slight_smile:

1 Like

Thanks again!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.