Expected reference to a dyn trait, found a reference to a struct implementig that very trait... Umh πŸ€”

Hi,

I am trying to design a generic game rule that manages a generic game action and I ended up with this code (simplified here):

trait Action{}

trait Rule<'a>{
    type Target: Action + 'a;
}

struct FireAction;

impl<'a> Action for &'a FireAction{}

struct FireRule;

impl<'a> Rule<'a> for FireRule {
    type Target = &'a FireAction;
}

struct SkipAction;

impl<'a> Action for &'a SkipAction{}

struct SkipRule;

impl<'a> Rule<'a> for SkipRule {
    type Target = &'a SkipAction;
}

fn main() {
    let _v: Vec<&dyn Rule<Target = &dyn Action>> = vec![&FireRule{}, &SkipRule{}];
}

Why the compiler is complaining with something like the following?

note: expected reference `&dyn Action`
      found reference `&FireAction`

I was thinking that &FireAction is a &dyn Action.

Thank you.

No, they are different types, and neither is a subtype of the other. You can convert an &FireAction into an &dyn Action, but they are different types.

1 Like

Uhm... Could I at least say that &FireAction is implementing &dyn Action? Or maybe I cannot since actually it is &FireAction implementing Action?

Well, it can't implement &dyn Action because that's a type, not a trait.

2 Likes

TouchΓ© :blush: You're right, I got it.

My idea was to have a list of rules, each of that can manage one type of action and I was thinking to use the chain of responsibility pattern in order to identify which rule can manage an action and then execute the action according to the rule, and I was trying to keep rule and action objects quite abstract (so traits).

Another solution could be making Action an enum and then Rule could drop the associated type. I still can use the chain of responsibility approach using a if let or match (I am not such a fan of this approach, but I know... :grinning_face_with_smiling_eyes:)

You might be confused by the fact that Rust's type system doesn't treat references transparently.

FireAction, &'a FireAction, dyn Action, and &'a dyn Action are 4 distinct types - they don't have any particular relation to each other as far as the type system is concerned. This is unlike some other languages where references are not considered "proper" types and a reference "might as well be" the thing referenced as far as the type system is concerned. In Rust references are only transparent in deref coercion and as the receiver of a method or field access (autoderef).

Note also that this implies FireAction does not implement Action, because you only implemented it for &'a FireAction.

5 Likes

Another place this sometimes surprises people, related to this thread, is that references to a dyn Trait (e.g. &dyn Trait) do not implement Trait. (You can add an implementation if sensible. Some std traits have blanket implementations.)

Now I have a further question.

Why this code compiles?

trait Action{}

struct ActionA;

impl Action for ActionA{}

struct ActionB;

impl Action for ActionB{}

fn main() {
    let _v: Vec<&dyn Action> = vec![&ActionA{}, &ActionB{}];
}

If I got it right &dyn Action, &dyn ActionA, &dyn ActionB are different types, and subtyping only exists between traits, so they have not any subtyping relationship, furthermore variance rules only apply between subtypes, so I cannot use this (nice) rule: F is covariant if F<Sub> is a subtype of F<Super> (where F in this case would be &dyn)

Argh! I am happy that it works, but I cannot understand why :woozy_face:

Here, &ActionA{} and &ActionB{} are coerced to be of type &dyn Action via an unsized coercion. That couldn't happen in the previous example because a coercion of the associated type Target is not possible.

Subtyping doesn't technically exist between traits, since traits aren't types. The subtyping and variance rules are mainly designed to specify where you can substitute one lifetime for another in a type, so they don't apply here. What you are thinking of are supertraits and subtraits, which are different.

3 Likes
    let _v: Vec<&dyn Action> = vec![
                &ActionA {},
                &ActionB {}
//              ^ all three are behind a reference
    ];
// From here I'm going to just talk about `ActionA` when I mean
// `ActionA` and `ActionB`

Here, &ActionA is being coerced into &dyn Action. This is only possible because ActionA implements Action. (That's the unsized part of unsized coercion: ActionA is sized and dyn Action is not.)

(More about the trait bounds of unsized coercion which is probably more confusing than helpful at this point. Perhaps just take my word that...)

More details; note the part about Unsize being a compiler-implemented trait only. I decided to put this behind an expansion because, honestly, I don't think it's something you think about once you get the hang of things. I didn't even know how it worked until long after I got the hang of things, in fact.

But anyway, what are the implicit trait bounds here? The important one is that ActionA: Action, so that ActionA can coerce into dyn Action, because that's how Unsize works with dyn Action. You can usually think of the rest as this coercion happening "behind the reference/pointer" IMO. But for completeness they are:

  • Can &ActionA coerce into &dyn Action?
    • Yes if &ActionA: CoerceUnsized<&dyn Action>
    • There is a blanket implementation that applies if ActionA: Unsize<dyn Action>
  • Does ActionA: Unsize<dyn Action> then?
    • Yes, because the compiler says so
    • ActionA: Action is a requirement for the compiler to say so

In particular, there is no &ActionA: Action or &dyn Action: Action bound. And in fact, neither &ActionA nor &dyn Action implement Action, but that's okay in the example -- because nothing required them to.

Where might that come up, then? Behind generics:

//             vvvvvvvv dispose of the implicit `Sized` bound
fn f<T: Action + ?Sized>(_v: Vec<&T>) {}

// Can't get rid of the `Sized` bound here because `Vec` depends on it
// Which means you can't have a `Vec<dyn Action>`
fn g<T: Action /* + Sized */>(_v: Vec<T>) {}

fn main() {
    let u: Vec<ActionA> = vec![];
    let v: Vec<&ActionA> = vec![];
    let w: Vec<&dyn Action> = vec![&ActionA{}, &ActionB{}];

    f(u); // This fails as we don't have a `Vec` of references
    f(v); // This is okay as `ActionA: Action`
    f(w); // This is okay as `dyn Action: Action`
 
    g(u); // This is okay as `ActionA: Action`
    g(v); // This fails as `&ActionA` does _not_ implement `Action`
    g(w); // This fails as `&dyn Action` does _not_ implement `Action`
}

One possible fix if you find yourself in a situation like fn g is:

// Applies to `&ActionA`, `&dyn Action`, then recursively `&&ActionA` etc
impl<T: Action + ?Sized> Action for &T {}

But this isn't always sensible (e.g. maybe Action has a method that takes a &mut self).

2 Likes

Thank you, I am so confused with subtyping in Rust :cry:
I think that I have to replace the meaning of : with "implements" instead of "is a subtype of"

Anyway if I understood correctly from the link posted in the reply above (thanks @jameseb7), struct generic type parameter can be an unsized coercion site (while normally is not allowed), and in my case T is &ActionA and U is &dyn Action, then... Then I didn't get which rule applies :flushed:, I mean I can grasp, since it make sense that it can be coerced, but I am not sure to got it... Is this one?

  • T to dyn U , when T implements U + Sized , and U is object safe.

Edit:
Uhm... It should not be that one... Since &ActionA does not implement &dyn Action

Yes, Rust is not an inheritance based language. What you have is two concepts: types and traits. Types can implement traits. Subtyping doesn't exist in Rust. The things that look like subtyping are actually built-in conversions that happen automatically in some cases.

(okay, there are some technicalities with lifetimes that are called subtyping, but it has nothing to do with traits, and it is best if you ignore it when trying to understand traits)

3 Likes

This may be a bit of XY or technically out of topic, but I think it ought to be quite useful, since it will be yielding actionable advice after the nice (type-)theoretical considerations already mentioned in this post.

The situation

So, for starters, I think you have simplified your example a bit too much: if there is a Target type, then I suspect you actual use case has method using it:

trait Rule<'a> {
    type Target : Action + 'a;

    /// Let's assume you have this kind of method:
    fn get_action (self: &'a Self)
      -> Self::Target
    ;
}

And for dyn Action to be useful, let's also add a method to it:

trait Action {
    fn act (self: &'_ Self)
    ;
}

Your objective is to, from a bunch of stuff implementing Rule, to get a collection of these rules to potentially end up making their actions .act():

/* pseudo-code! */
let mut rules = vec![];
rules.push(&some_rule);
rules.push(&some_other_rule);
for rule in &rules {
    rule.get_action().act();
}

At that point, indeed, you'll need to use dyn traits to "collect" different types within the same Vec, and the associated type prevents you from doing so.

The solution

… is to use a helper trait, which will perform the intermediary coercion for you:

trait DynRule<'a> {
    fn dyn_get_action (self: &'a Self)
      -> Box<dyn 'a + Action>
    ;
}

impl<'a, T> DynRule<'a> for T
where
    T : Rule<'a>,
{
    fn dyn_get_action (self: &'a Self)
      -> Box<dyn 'a + Action>
    {
        Box::new(self.get_action())
    }
}

And, as you can see, DynRule<'a> no longer has an associated type, so you are free to collect &'_ dyn DynRule<'_>:

/* implementors */
struct FireAction;
impl<'a> Action for &'a FireAction {}

struct FireRule;
impl<'a> Rule<'a> for FireRule {
    type Target = &'a FireAction;

    /// Let's assume you have this kind of method:
    fn get_action (self: &'a Self)
      -> Self::Target
    {
        &FireAction {}
    }
}

struct SkipAction;
impl<'a> Action for &'a SkipAction {}

struct SkipRule;
impl<'a> Rule<'a> for SkipRule {
    type Target = &'a SkipAction;

    /// Let's assume you have this kind of method:
    fn get_action (self: &'a Self)
      -> Self::Target
    {
        &SkipAction {}
    }
}
/* β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€” */

fn main ()
{
    let rules: Vec<&dyn DynRule<'_>> = vec![&FireRule {}, &SkipRule {}];
    for rule in rules {
        rule.dyn_get_action().act();
    }
}

Note that given that you are dealing with &'a …-shaped return types, that is, if all the returned actions are returned by reference, then the Box can be skipped by yielding &dyn Actions instead, provided the implementor of Action is not the returned reference but its referee. This also allows getting rid of the lifetime parameter. Playground

2 Likes

There's another rule that effectively says

  • &T to &dyn U ,when T implements U + Sized, and U is object safe

(And similarly for other pointer types, like Box<T>, Rc<T>, etc.)

1 Like

Thank you for your suggestion, I need to ponder over your reply some more time... Anyway, yes, I maybe oversimplified my original example.

The idea is to have generic rule that have two main functionalities:

  • Assert if it can manage an Action
  • Execute the Action according to the its own rule logic

So something like the following:

trait Action {
   fn source(&self) -> ActionSource; // ActionSource is an enum
}

trait Rule<'a> {
    type ActionType: Action + 'a;

    fn can_manage(&self, action: &dyn Action) -> bool;
    fn execute(&self, action: &dyn Action, game_state: &GameState)-> GameState;
}

Action at the moment is more a data container that provides information/argument of the action to be performed, it doesn't provide any logic, so it doesn't sound so much a trait (it just provide the source of the action), but maybe in the future I could add some logic to it.

Anyway I know that I could simplify my abstraction and my design making the Action an enum and functions can_manage and execute could use a match to extract the action data.

Consider that I am (also) overcomplicating my design in order to challenge myself and try to get better some advanced Rust topics.

1 Like

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.