No method error while method was defined in blanket implementation

So I have a blanket implementation :

impl<T> Propagator for T
    where T: Component
{
    default fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
        self.update(engine, delta_time);
    }
}

(Component is an other trait)

Then, in an other module, I have some code that looks like this :

for i in self.children.iter_mut() {
    i.deref_mut().propagate_update(engine, delta_time);
}

Where i is a Box<dyn Component>

When I try to compile my program, I get the following error :

22 |             i.deref_mut().propagate_update(engine, delta_time);
   |                           ^^^^^^^^^^^^^^^^ method not found in `&mut (dyn engine::component::Component + 'static)`

Propagator should be implemented for every Component, so why doesn't that work ?

A pointer to a trait object, e.g. Box<dyn Component> is composed of two pointers:

  • a pointer to the instance of type T implementing the trait
  • a pointer to a vtable. For each method in Component and its supertraits, the vtable contains a function pointer to the actual implementation of the function

In your case, Propagate is a subtrait of Component therefore the vtable in Box<dyn Component> doesn't have any information about methods defined in Propagator even though your underlying type T implements it. You have multiple work-around:

  • Use the solution proposed by @Yandros in your last topic. Since Component is a subtrait of Propagator in his solution, a Box<dyn Component> will be able to call methods in the supertrait
  • Change the type of Box<dyn Component> to Box<dyn Propagator>

You can also coalesce two traits into a single trait, effectively merging the two vtables. This also works without supertraits relationship:

// &dyn PropagatorComponent can access methods
// defined in both Component and Propagator
trait PropagatorComponent: Component + Propagator {}
impl <T> PropagatorComponent for T where T: Component + Propagator {}

Note that in general you could also define a method in the supertrait to perform the conversion however you'd also have to make the Propagator trait public

pub trait Component: Sync {
    // ...
    fn as_propagator(&mut self) -> Option<&mut dyn Propagator>;
}

impl Component for NodeComponents {

    fn as_propagator(&mut self) -> Option<&mut dyn Propagator> {
        Some(self)
    }
}

// you can now convert from &dyn Component to &dyn Propagator
// for i in self.children.iter_mut() {
//    i.as_propagator().unwrap().propagate_update(engine, delta_time)
//}
2 Likes

Now it works and I don't really understand why.

I got this :

pub struct NodeComponent {
    children: Vec<Box<dyn Component>>
}

impl Component for NodeComponent {
    fn new() -> Self {
        NodeComponent {
            children: vec![]
        }
    }
}

impl Propagator for NodeComponent {
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
        self.update(engine, delta_time);

        for i in self.children.iter_mut() {
            i.deref_mut().propagate_update(engine, delta_time);
        }
    }
}

pub(crate) trait Propagator: Send + Component{
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {

    }
}

pub trait Component: Send {
    fn new() -> Self where Self: Sized;

    fn init(&mut self, engine: &mut Engine) {}
    fn update(&mut self, engine: &mut Engine, delta_time: f64) {}
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
        self.update(engine, delta_time);
    }
}

impl<T: Component> Propagator for T
{
    default fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
        self.update(engine, delta_time);
    }
}

+ some other unrelated code and it compiles just fine. I litteraly don't know what changed.

EDIT : Now I see it, I forgot I added a propagate_update method to component as well

I think I'll make it Box<dyn Propagator> then...

Adding propagate_update in Component definitely works however you get back to the issue you had in your previous topic where you wanted to disallow library users to override propagate_update in Component

I'm going with the Box<dyn Propagator> method. It's weird Rust doesn't provide some easy way to make part of a trait private I think...

Thank you very much for your help !

default is not a keyword in Rust. How would this even compile?

default is a keyword if you active the nightly specification feature, which I use

You need to include ?Sized in the where clause of your blanket impl, otherwise it won’t be implemented for dyn Component:

impl<T> Propagator for T where T: Component + ?Sized { /* ... */ }
1 Like

It worked ! thank you ! did you know that @esseivaju ?

One warning here: dyn Component is a distinct type, separate from the concrete types it might dynamically dispatch to. If you’re using specialization to overwrite the blanket impl, the specialized version won’t apply through the dyn Component wrapper.

1 Like

So propagate_update will always refer to the default in the blanket implementation ? No way to override it ?

Specialization will let you override it for all dyn Component objects at once, but the only way to pierce the dyn veil and make a decision based on the type beyond a dynamic pointer is via the Any trait.

1 Like

So if I have a NodeComponent implementing Component, and I then implement Propagator for NodeComponent (overriding), and then reffer to that NodeComponent as a dyn Component, and call the propagate_update on that dyn Component, will it call the default or the overriding method ?

In that case, it’ll call the default implementation because you didn’t make a specific override implementation for dyn Component.

1 Like

But if I refer to it as dyn Propagator, will it call the overriden method ?

If you refer to it as dyn Propagator, it will use whatever Propagator implementation would have been used at the point the cast to dyn Propagator was made:

  • &SomeSpecificComponent as &dyn Propagator => default impl
  • &NodeComponent as &dyn Propagator => overridden impl
  • &dyn Component as &dyn Propagator => default impl, or maybe a compile error
1 Like

Ok thank you !

That is quite nice, I knew about the implicit Sized bound on type parameters and DST trait object type though I didn't realize that opting out of Sized in a blanket impl would also implement that trait for dyn Trait but this make sense.

1 Like