I'm trying to build a simple ECS storage structure that can be queried for iteration based on type combinations, with both &T
and &mut T
access patterns.
use std::cell::{Ref, RefCell, RefMut};
#[derive(Default)]
pub struct SimpleWorld {
column_1: RefCell<Vec<u8>>,
_column_2: RefCell<Vec<u16>>,
column_3: RefCell<Vec<u32>>,
_column_4: RefCell<Vec<u64>>,
}
trait World {
fn column<'w, T: Param<'w, Self>>(&'w self) -> T::Column<'w>
where
T::Column<'w>: 'w,
{
<T as Param<Self>>::column(self)
}
}
impl World for SimpleWorld {}
trait Param<'p, W: World + ?Sized>: Sized {
type Underlying;
type Column<'c>: 'c;
type Unpacked<'u>;
fn column(world: &'p W) -> Self::Column<'p>;
fn unpack<'a>(column: &'a mut Self::Column<'_>) -> Self::Unpacked<'a>;
}
impl<'p> Param<'p, SimpleWorld> for &'p u8 {
type Underlying = u8;
type Column<'c> = Ref<'c, [u8]>;
type Unpacked<'u> = &'u [u8];
fn column(world: &'p SimpleWorld) -> Self::Column<'_> {
Ref::map(world.column_1.borrow(), |c| c.as_slice())
}
fn unpack<'a>(column: &'a mut Self::Column<'_>) -> Self::Unpacked<'a> {
column.as_ref()
}
}
impl<'p> Param<'p, SimpleWorld> for &'p mut u32 {
type Underlying = u32;
type Column<'c> = RefMut<'c, [u32]>;
type Unpacked<'u> = &'u mut [u32];
fn column(world: &'p SimpleWorld) -> Self::Column<'_> {
RefMut::map(world.column_3.borrow_mut(), |c| c.as_mut_slice())
}
fn unpack<'a>(column: &'a mut Self::Column<'_>) -> Self::Unpacked<'a> {
column.as_mut()
}
}
trait System<'p, W: World, Input> {
fn run(&mut self, world: &'p W);
}
impl<'p, F> System<'p, SimpleWorld, (&u8, &mut u32)> for F
where
F: FnMut(&u8, &mut u32),
{
fn run(&mut self, world: &'p SimpleWorld) {
let mut p1 = <&u8 as Param<'p, SimpleWorld>>::column(world);
let mut p2 = <&mut u32 as Param<'p, SimpleWorld>>::column(world);
let p1_slice = <&u8 as Param<'_, SimpleWorld>>::unpack(&mut p1);
let p2_slice = <&mut u32 as Param<'_, SimpleWorld>>::unpack(&mut p2);
for (a, b) in p1_slice.into_iter().zip(p2_slice.into_iter()) {
self(a, b)
}
}
}
fn main() {
let world = SimpleWorld {
column_1: vec![1, 2, 3].into(),
_column_2: vec![4, 5, 6].into(),
column_3: vec![7, 8, 9].into(),
_column_4: vec![10, 11, 12].into(),
};
(|a: &u8, b: &mut u32| println!("{} {}", *a, *b)).run(&world);
}
This code works fine when System
is implemented for functions with concrete types, but when I go to generalize it by replacing that impl
with this one:
impl<'p, F, T1, T2> System<'p, SimpleWorld, (T1, T2)> for F
where
F: FnMut(T1, T2),
T1: Param<'p, SimpleWorld> + 'p,
T2: Param<'p, SimpleWorld> + 'p,
<T1 as Param<'p, SimpleWorld>>::Unpacked<'p>: IntoIterator<Item = T1>,
<T2 as Param<'p, SimpleWorld>>::Unpacked<'p>: IntoIterator<Item = T2>,
{
fn run(&mut self, world: &'p SimpleWorld) {
let mut p1 = <T1 as Param<'p, SimpleWorld>>::column(world);
let mut p2 = <T2 as Param<'p, SimpleWorld>>::column(world);
let p1_slice = <T1 as Param<'_, SimpleWorld>>::unpack(&mut p1);
let p2_slice = <T2 as Param<'_, SimpleWorld>>::unpack(&mut p2);
for (a, b) in p1_slice.into_iter().zip(p2_slice.into_iter()) {
self(a, b)
}
}
}
I get the following error about the lifetimes of p1
(and p2
):
error[E0597]: `p1` does not live long enough
--> <source>:75:63
|
63 | impl<'p, F, T1, T2> System<'p, SimpleWorld, (T1, T2)> for F
| -- lifetime `'p` defined here
...
75 | let p1_slice = <T1 as Param<'_, SimpleWorld>>::unpack(&mut p1);
| ---------------------------------------^^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `p1` is borrowed for `'p`
...
81 | }
| - `p1` dropped here while still borrowed
Is there something I can do with these where
clauses to preserve the lifetimes here? It seems to me like p1
should live long enough as a Ref
or RefMut
, but for whatever reason I can't enforce that. Failing trait clauses, is there an unsafe way to get around this lifetime definition limitation without UB?