leudz / shipyard

Entity Component System focused on usability and flexibility.
Other
750 stars 45 forks source link

Help me some lifetime issue.. #177

Closed PudgeKim closed 1 year ago

PudgeKim commented 1 year ago

I know It's not a shipyard related issue but I need some help.. Please understand the code below looks weird. The original code is quite long so I needed to summarize.

#[derive(Borrow, BorrowInfo)]
struct MyViewMut<'a> {
    my_components: ViewMut<'a, MyComponent>
}

impl MyViewMut<'_> {
    fn my_method<'a, 'b: 'a>(
        &mut self,
        areas: impl Get<Out=&'a Area> + Copy,
        my_bags: &'b mut ViewMut<MyBag>,
        cnt: i32
    ) -> Result<(), ()> {
        if cnt == 3 {
            return Ok(());
        }

        inner_func(&*my_bags, areas); // I need to cast ! &*
        (0..10)
            .for_each(|_| {
                self.my_method(areas, my_bags, cnt+1).unwrap();
            });

        return Ok(());
    }
}

#[derive(Component)]
struct MyComponent();

#[derive(Component)]
struct MyBag {
    data: HashMap<String, String>
}

#[derive(Component)]
struct Area {
    coordinates: Vec<i32>
}

fn inner_func<'a>(
    my_bags: impl Get<Out=&'a MyBag> + Copy,
    areas: impl Get<Out=&'a Area> + Copy,
) {
    let _ = my_bags;
    let _ = areas;
}

As you see, I have recursive function and this function calls other function which needs impl Get<Out=...>. But this code cannot be compiled. Here's the error message.

   |
15 |     fn my_method<'a, 'b: 'a>(
   |                  -- lifetime `'a` defined here
...
27 |             .for_each(|_| {
   |                       --- lifetime `'1` represents this closure's body
28 |                 self.my_method(areas, my_bags, cnt+1).unwrap();
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`
   |
   = note: closure implements `FnMut`, so references to captured variables can't escape the closure

I can compile this code when I change the code as below.

impl MyViewMut<'_> {
    fn my_method<'a>(
        &mut self,
        areas: impl Get<Out=&'a Area> + Copy,
        my_bags: &mut ViewMut<MyBag>, // here!!
        cnt: i32
    ) -> Result<(), ()> {
        if cnt == 3 {
            return Ok(());
        }

        inner_func(&*my_bags, areas);
        (0..10)
            .for_each(|_| {
                self.my_method(areas, my_bags, cnt+1).unwrap();
            });

        return Ok(());
    }
}

fn inner_func<'a, 'b>(
    my_bags: impl Get<Out=&'b MyBag> + Copy, // add one more lifetime
    areas: impl Get<Out=&'a Area> + Copy,
) {
    let _ = my_bags;
    let _ = areas;
}

Is there anyway to compile this code without adding one more lifetime in inner_func? (If inner_func has more parameters, I need to add many lifetime and I think it doesn't seem good.) What's the best and general way to fix this code?

leudz commented 1 year ago

I'm afraid there is no other way. I've been trying to find a way but I think the core of the issue is impl Get<Out=&'a Area> and it can't be changed. Usually with shared references the compiler can bend the lifetime but I don't think it can in this case.

There is in theory a way to make it work: having a function to go from &ViewMut to View. This way you could change impl Get to View. But I'm not sure if this safe, none of the std types have it. Tokio has something close but it takes the ViewMut by value.

leudz commented 1 year ago

I created a post on the forum asking if the function would be safe https://users.rust-lang.org/t/rwlockwriteguard-as-rwlockreadguard/94298.

leudz commented 1 year ago

Ok so I have not one but two ways to make it work.

  1. Use the new ViewMut::as_view to get a View and use it to call inner_func.
  2. Instead of impl Get<Out=&'a Area> + Copy use &SparseSet<Area>. I added an impl Get for &SparseSet and will work for both View and ViewMut, you might have to add a & or two.