Lifetime Problems With `FnMut` that Takes Generic Arguments With Lifetimes

I'm having trouble figuring out why Rust things that some variables won't live long enough. I was able to boil down my code to a playground sample:

use std::{
    any::{Any, TypeId},
    collections::HashMap,
    marker::PhantomData,
    sync::{Arc, RwLock, RwLockReadGuard},
};

/// Container for resources
struct World {
    resources: HashMap<TypeId, UntypedResource>,
}

/// A type-erased resource
type UntypedResource = Arc<RwLock<Box<dyn Any>>>;

/// A Wrapper around an untyped resource that is typed
struct Resource<T> {
    untyped: UntypedResource,
    _phantom: PhantomData<T>,
}

impl<T> Resource<T> {
    pub fn borrow(&self) -> ResourceRef<T> {
        ResourceRef {
            guard: self.untyped.read().unwrap(),
            _phantom: PhantomData,
        }
    }
}

/// A read-only borrow of a [`Resource<T>`].
///
/// Derefs to `T`.
struct ResourceRef<'a, T> {
    guard: RwLockReadGuard<'a, Box<dyn Any>>,
    _phantom: PhantomData<T>,
}
impl<'a, T: 'static> std::ops::Deref for ResourceRef<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.guard.downcast_ref().unwrap()
    }
}

/// Trait for anything that may be passed to a system as a parameter.
trait SystemParam<'a> {
    /// The intermediate state that must live during the run of the system
    type State;
    /// Initialize any resources used by the param
    fn initialize(world: &mut World);
    /// Get the state from the world
    fn get_state(world: &World) -> Self::State;
    /// Borrow the system parameter from the state
    fn borrow(state: &'a mut Self::State) -> Self;
}

/// System param that borrows a resource.
///
/// Derefs to `T`.
struct Res<'a, T: 'static>(ResourceRef<'a, T>);
impl<'a, T: 'static> std::ops::Deref for Res<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl<'a, T: Default + 'static> SystemParam<'a> for Res<'a, T> {
    type State = Resource<T>;
    fn initialize(world: &mut World) {
        let type_id = TypeId::of::<T>();
        world
            .resources
            .entry(type_id)
            .or_insert_with(|| Arc::new(RwLock::new(Box::new(T::default()))));
    }
    fn get_state(world: &World) -> Self::State {
        Resource {
            untyped: world.resources.get(&TypeId::of::<T>()).unwrap().clone(),
            _phantom: PhantomData,
        }
    }
    fn borrow(state: &'a mut Self::State) -> Self {
        Res(state.borrow())
    }
}

/// A system
pub struct System {
    init: Box<dyn FnMut(&mut World)>,
    run: Box<dyn FnMut(&World)>,
}

/// Implemented for things that can convert to a system
pub trait IntoSystem<T> {
    fn system(self) -> System;
}

impl<'a, F, T> IntoSystem<T> for F
where
    F: FnMut(T) + 'static,
    T: SystemParam<'a> + 'static,
{
    fn system(mut self) -> System {
        System {
            init: Box::new(|world| {
                T::initialize(world);
            }),
            run: Box::new(move |world| {
                let mut state = T::get_state(world);

                self(T::borrow(&mut state))
            }),
        }
    }
}

fn sys1(b: Res<bool>) {
    dbg!(*b);
}

fn main() {
    let _s = sys1.system();
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `state` does not live long enough
   --> src/main.rs:112:32
    |
99  | impl<'a, F, T> IntoSystem<T> for F
    |      -- lifetime `'a` defined here
...
112 |                 self(T::borrow(&mut state))
    |                      ----------^^^^^^^^^^-
    |                      |         |
    |                      |         borrowed value does not live long enough
    |                      argument requires that `state` is borrowed for `'a`
113 |             }),
    |             - `state` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error

What I can't figure out is why state is still borrowed after the function self has returned.

I think it's a problem with me not being able to specify the lifetime bounds how I want to.

For instance, what I think I want to do is remove the 'a lifetime from the impl and instead have F something like:

where for<'a> F: FnMut(T: SystemParam<'a>)

I need to make T range over the lifetime associated to the function call, but I can't figure out how to represent that.

It's worth noting that I found a similar approach that fixes the lifetime issue, by specifying an associated Param<'a> type for SystemParam using GATs, but the issue is that it allows for multiple different implementations of SystemParam to have the same Param value, so it requires type annotations every time you call .system(), which is no good:

( playground )

T::borrow takes a &'a mut T::State, and at line 99 you're trying to define this for every possible 'a. For this to work, it would have to make sense even for 'static, for example.

Moreover, I'm not thinking of a direct way to fix it, because you want something like a

for<'a> T<'a>: SystemParam<'a>

But there is no such thing as a non-associated generic type constructor (T<'a>). There's no direct way to name the T that corresponds to the arbitrarily short lifetime needed in the code.

You can emulate bounds like that using a helper trait with GAT, but it's still rejected. I think what's going on now is that the lifetimes of state and borrow get "intermingled", so that the compiler believes it's possible for either one to observe the other, and there's no workable drop order. (Try swapping the declaration order and seeing the change in error output.)

Also as you can see in main, inference tends to fall apart at that level of abstraction.

Thanks for looking at it @quinedot.

I did some digging into Bevy, and how it did this, because I'm essentially copying Bevy's idea, and I think I've found it!

Apparently there's a simple way to force the type inferrence with the second option I found above:

Here's the working version: ( playground ).

The only difference was changing this:

impl<F, T> IntoSystem<T> for F
where
    F: for<'a> FnMut(<T as SystemParam>::Param<'a>) + 'static,
    T: SystemParam + 'static,
{
    // ...
}

into this:

impl<F, T> IntoSystem<T> for F
where
    F: for<'a> FnMut(<T as SystemParam>::Param<'a>) +
        /* key bound: */ FnMut(T) +
        'static,
    T: SystemParam + 'static,
{
    // ...
}

By saying that F has to implement both FnMut(T) and FnMut(<T as SystemParam::Param<'a>), it fixes type inference and strangely enough allows us to do what we need.

I'd never have thought of that.

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.