Static life-time issue with engine framework's node access

I have my own framework crate that stores all of its nodes inside one vector as a trait object to be allocated/read/written by application logic and engine.

I have trouble to understand what Rust does not understand with this write function for such node:

pub fn write
<
    T: NodeEvent + 'static,
    TFunction: FnOnce(&mut T)
>
(
    handle: &Handle<T>,
    function: TFunction
)
{
    let guard_allocations = NODE_ALLOCATIONS.read().unwrap();
    let mut node_guard = guard_allocations.get(handle.index.0).unwrap().write().unwrap();
    let node_any: &mut dyn Any = &mut &mut*node_guard.node as &mut dyn Any;
    let concrete_data = node_any.downcast_mut::<T>().unwrap();
    function(concrete_data);
}

It says:

error[E0597]: guard_allocations does not live long enough
--> src/lib.rs:1098:26
|
1098 | let mut node_guard = guard_allocations.get(handle.index.0).unwrap().write().unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
1099 | let node_any: &mut dyn Any = &mut &mut*node_guard.node as &mut dyn Any;
| ------------------------- cast requires that guard_allocations is borrowed for 'static
...
1102 | }
| - guard_allocations dropped here while still borrowed

let guard_allocations is type
static NODE_ALLOCATIONS: RwLock<Vec<RwLock<Allocation>>> = RwLock::new(Vec::new());

Allocation is type

struct Allocation
{
    id: AllocationID,
    node: Box<dyn NodeEvent + Send + Sync>
}

How can anything inside a function live not enough for something outside a function like static NODE_ALLOCATIONS? Especially behind a guard?

Bad Working Solution
I need Any trait so that I can do a downcast_mut() to the concrete_data of that node and I get that via custom any() and any_mut() via the NodeEvent trait.

Which means every time I define a new node inside the app logic for the game engine framework like say Door or for the engine like say MeshInstance. I would have to also implement these two very simple any functions all the time. Rust does not allow any additional traits inside that trait object unless it is some auto trait. And a trait default for them causes a sized issue with the default definition or when calling:

error: the `any_mut` method cannot be invoked on a trait object
    --> /media/omnissiah/MyData/_NerlinGames/NerhimUniverse/Nerhim/nokden/src/lib.rs:1202:49
     |
1202 |             let concrete_data = node_guard.node.any_mut().downcast_mut::<T>().unwrap();
     |                                                 ^^^^^^^
...
1306 |     -> &mut dyn Any where Self: Sized
 |     

Previously used bad solution. Just define for every node outside the framework crate instead of using their trait default:

impl
NodeEvent
for [NodeType] // Players, Doors, Meshes, Audio, etc.
{
    fn console() ... 
    fn filing() ...
    
    fn any
    (
        &self,
    )
    -> &dyn Any
    {
        self
    }

    fn any_mut
    (
        &mut self,
    )
    -> &mut dyn Any 
    {
        self
    }
}

It is not the end of the world having to do that. But it is very stupid since that code never changes and always just returns self as Any.

Wrapping into unsafe block to shut-up the compiler also does not seem to work.

As a side note. The nodes have to be stored in a static manner so that they are readable by the Drop trait. It essentially does garbage collection that way. When a handle for a node is dropped the framework knows that the node allocation for that handle also needs freeing.

Sounds like you want trait upcasting. Until then, the any_mut way is idiomatic (and still will be when a supertrait bound is too restrictive for other reasons).

In your first example code you're trying to coerce a &mut &mut ... to a &mut dyn Any, that is, trying to coerce the inner &mut into dyn Any. Even with upcasting you'd need to get rid of the nested &mut; that may be the cause of the 'static error.

Wrapping stuff in unsafe just to shut up the compiler is a good way to get UB.

Ah. Yes. Thanks for bringing this up. Yes it works that way I just tried. I encountered it in the compiler errors but since it mentioned unstable feature I just tried to ignore it and went on trying all my other options first. Seems though that I am out of options now. Well. Languages wise that is.

Technically I could call filing() and console() from app or engine instead of the framework crate I think. So simply replace NodeEvent with Any. But also technically that would displace function calling into the core app nodes and core engine nodes instead of just having it in one single place inside the framework so that it can be iterated through. I do not think that this is such a clean way to do things.

Yes. I am afraid that this is called desperation as you run out of options. :confused:

The unstable feature should be fine for now. It will get stabilized at some point in the future.

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.