Dyn trait objects from book doesn't work for generics

Hello,

It is related to:
https://doc.rust-lang.org/book/ch17-02-trait-objects.html
And the playground :

It seems only one type is allowed, but the example seems to tell that it compiles without any error!!

When the Screen type is not generic — or more precisely, when the field components: Vec<Box<dyn Draw>> is not generic — the compiler knows what type the Vec's elements will be, and will automatically figure out that each one in the vec![] needs to be coerced to Box<dyn Draw>.

When it is generic, the compiler cannot be certain what you wanted. In fact, your code doesn't mention dyn Draw anywhere at all, so the compiler is definitely not going to introduce dynamic dispatch without your having asked for it.

A sufficient solution here is to give a type to your variable declaration:

let screen: Screen<Box<dyn Draw>> = Screen {

(In other cases of using vec![], you might need to add as Box<dyn Draw> to one of the vector elements to explicitly coerce it to the wanted type.)

You'll also get an error that Box<dyn Draw> doesn't implement Draw, which is true; fix that by adding impl Draw for Box<dyn Draw>, or even better, a blanket impl for all kinds of Boxes:

impl<T: ?Sized + Draw> Draw for Box<T> {
    fn draw(&self) {
        (**self).draw()
    }
}

Many Rust traits are implemented for Boxes and & references in this forwarding fashion; it makes them usable in a broader variety of circumstances (not just dyn) and you should consider adding such impls whenever you create a trait.

6 Likes

Yet another option would be to just specify what type of Screen you want with:

let screen = Screen::<Box<dyn Draw>> {

I wonder which version is considered more ideomatic.

1 Like

Thank you for your answers.
But I think the doc should explicitly show that the code throws an error.

It's not super clear but the book uses this earlier version of Screen

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

If you define it like this your original code will work too.

2 Likes

Are you saying that when the example is changed from using dyn Draw to using <T: Draw>, the doc should say that you can't combine the approaches from the two examples?

It is just showing two different ways of doing things. The two examples are divided by a paragraph starting with "This works differently from defining a struct that uses a generic type parameter with trait bounds...".

Trying to combine the two approaches is something you tried to do, but the doc didn't talk about at all.

No the doc later goes on to use the Screen struct in further examples without showing which definition is used but it's clearly using the first version (the one with dyn Draw). It could be confusing to some people.

1 Like

You're right that it could easily be confusing and should be improved.

Looking again, I think there is an error in the paragraph that transitions back from generics to trait objects, just before the next section you referred to:

On the other hand, with the method using trait objects, one Screen instance can hold a Vec<T> that contains a Box<Button> as well as a Box<TextField>. Let’s look at how this works, and then we’ll talk about the runtime performance implications.

Shouldn't "hold a Vec<T>" be "hold a Vec<Box<dyn Draw>>"?

Yeah I saw that too and I think it would already help if this was changed in the way you propose.

It's not technically wrong but it sure is misleading.

Agreed. I'll create a doc issue for it tomorrow.

1 Like

I created a new issue for this and also requested reopening an older issue.

Noice. Very well written issue.

Sorry for the fight !! :wink:

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.