Skip to content

Example in new book uses invalid trait object Box<dyn Command + Clone> #2423

@Seldom-SE

Description

@Seldom-SE

Describe the issue

In the Designing Components chapter, under Storing Functions Inside of Components, the example uses Box<dyn Command + Clone>. Unfortunately, this type doesn't compile:

error[E0225]: only auto traits can be used as additional traits in a trait object
 --> src/main.rs:5:31
  |
5 | fn bar(bar: Box<dyn Command + Clone>) {}
  |                     -------   ^^^^^ additional non-auto trait
  |                     |
  |                     first non-auto trait
  |
  = help: consider creating a new trait with all of these as supertraits and using that trait here instead: `trait NewTrait: bevy::prelude::Command + Clone {}`
  = note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>

The error's proposed solution doesn't work either:

trait NewTrait: Command + Clone {}

fn bar(bar: Box<dyn NewTrait>) {}
error[E0038]: the trait `NewTrait` is not dyn compatible
 --> src/main.rs:7:17
  |
7 | fn bar(bar: Box<dyn NewTrait>) {}
  |                 ^^^^^^^^^^^^ `NewTrait` is not dyn compatible
  |
note: for a trait to be dyn compatible it needs to allow building a vtable
      for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
 --> src/main.rs:5:27
  |
5 | trait NewTrait: Command + Clone {}
  |       --------            ^^^^^ ...because it requires `Self: Sized`
  |       |
  |       this trait is not dyn compatible...

Proposed Solution

At work, when I ran into this problem, I'd make a subtrait like ClonableCommand: Command with a method fn clone(&self) -> Box<dyn ClonableCommand>. That would detract from the focus of the example, though. I guess this example needs a use case where each command is only used once, so doesn't need Clone. Maybe something like

#[derive(Component)]
struct Projectile {
    on_hit: Vec<Box<dyn Command>>,
}

In this case, when the projectile hits, you'd drain on_hit, running each command, and then despawn the entity.

Another solution would be to not encourage this pattern. Ime, it's rarely better than one-shot systems, but your mileage may vary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions