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();
}
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.