HRTB? problem workaround?

They is a container ( lets say hashmap ) of handlers. Each handler is object trait. Each Handler operates an Event. In simple case, Event has no references and the code works. But if Event has any references, then it does not. I suspect it's another manifestation of the HRTB bug. Any workaround without changing the architecture?

You can make your associated type match the trait by removing the HRTB:

-    type ReceiversMap = HashMap<usize, Box<HandlerOfEventDyn>>;
+    type ReceiversMap = HashMap<usize, Box<dyn HandlerInterface<Event1<'a>> + 'static>>;

This does make the associated type less general, and makes it vary based on the lifetime 'a.

2 Likes

That solution works. But compiler throws exception "implementation of ReactorInterface is not general enough" if ReactorInterface has a mutable associated function then. Please have a look. Why is that so? Is it really related to the bug? Does it has a solution?

Same problem for your code, @quinedot.

dyn for<'any> could be used only on places where trait should be, In a struct as a field dyn Trait<'a> could only be used.

Attempts to eliminate either of the types fail. What is a proper solution?

I am thinking about using pointer instead of reference here:

pub struct Event1<'a> {
    a: &'a u32,
}

But even if it will remove the blocker, I don't feel it's a nice workaround.

Other thoughts I have using dyn more extensively. Although not sure how. I made such an attempt, but encountered the same problem on another level.

Is that a proper of my solution to use pointers instead of references?

pub struct Event1 {
    a: *mut u32,
}

impl Event1 {
    pub fn a<'a>(&'a self) -> &'a u32 {
        unsafe { std::mem::transmute::<_, &'a _>(self.a) }
    }

    pub fn a_mut<'a>(&'a mut self) -> &'a mut u32 {
        unsafe { std::mem::transmute::<_, &'a mut _>(self.a) }
    }
}

But what to do if field a has a ref?..

struct Struct1<'a> {
    b: &'a i32,
}

pub struct Event {
    a: *mut Struct1<x>,
}

The theoretical concern

As discussed in the Github issue, this is because, given some lifetime 'a, dyn Trait<'a> and dyn for<'any> Trait<'any> are distinct types, even if the latter subtypes the subtypes ("from being able to handle any lifetime we can restrict to being able to handle only one"). And since the other subtyping direction does not hold, once the types are erased ("from an erased type abouth which we only know it can handle that one specific lifetime 'a, we can't guarantee that it be able to handle any other lifetime").

And since a _mut accessor would allow overwriting the entries, from a temporary &mut borrow, we really can't let the user have access to &mut SubType and risk such an incorrect overwrite. It needs to be &mut ExactOriginalType, as in &mut dyn for<'any> …. The technical phrasing for this property (or rather lack thereof) / for this lack of leniency of &mut references it that &'_ mut Referee is not covariant in Referee (it's not contravariant either, fwiw, and the lack of both properties is called invariance).


Now, the surprising thing here is that something that can handle any lifetime is supposed to be able to handle one lifetime:

// not only do we have
dyn for<'any> Trait<'any> : for<'any> Trait<'any>
// but from "for all => any specific one", we also have, for some `'a`:
dyn for<'any> Trait<'any> : Trait<'a> 

that is, using existential impl notation:

dyn for<'any> Trait<'any> = impl ?Sized + Trait<'a>

And if we look at your snippet, you were not trying to use this property, but were instead trying to use the weaker:

dyn for<'any> Trait<'any> ⊊ dyn Trait<'a>

notice the lack of equality.

In other words, you may have used dyn one too many times. Hence the following change:

A possible solution

  • conceptually, it would be:

    trait ReactorInterface<Event>
    where
        Event : EventInterface>,
    {
        type ReceiversMap
        :
            for<'key> Index<
                &'key usize,
    -           Output = Box<dyn  'static + HandlerInterface<Event>>,
    +           Output = Box<impl 'static + HandlerInterface<Event>>,
            >
        ;
    

    (actually impl ?Sized + …).

In actual Rust code, this gives:

trait ReactorInterface<Event>
where
    Event : EventInterface>,
{
+   type Handler
+   :
+       ?Sized +
+       'static + HandlerInterface<Event>
+   ;

    type ReceiversMap
    :
        for<'key> Index<
            &'key usize,
-           Output = Box<dyn 'static + HandlerInterface<Event>>,
+           Output = Box<Self::Handler>,
        >
    ;

with it, we can pick:

  impl<'a> ReactorInterface<Event1<'a>> for Reactor {
+     type Handler = DynHandlerOfAnyEvent;

without any issue whatsoever, and the whole thing remains compatible with a mut getter :slightly_smiling_face:

2 Likes

Wow!! That works! Amazing! :smiley: Thank you, @Yandros

Trying to understand..

1 Like

I like the code style :sunglasses:

1 Like

Where can I read more about that? How to get more practice to understand such nuances of lifetimes better?

Hard to say, tbh; your situation was actually a rather subtle albeit very educational one. I may one day commit to my project of writing my own proper guide about these things :crossed_fingers:, and if/when that happens, I think I'll try to include a reduced variant of the issue in this thread:

  • the fact &mut impl/dyn for<'any> Trait<'any>
    can be "viewed as" a &mut impl Trait<'a>
    but not as &mut dyn Trait<'a>
    is an interesting thing to mention within an "advanced subtleties" section or something :smile:
3 Likes