I'm working on an ECS system, but in order to iterate across pairs of queries, I need to be able to fetch components only for a limited scope when executing a system. To do this, I'm trying to make a trait that can return direct references to components for a limited scope. I then use nested tuples to apply this trait to any combination of components. However, I can't to enforce the borrow with the compiler. I have two solutions, both break in different ways.
trait ScopedFetch {
type Result<'r>: 'r;
fn fetch<'scope>(&'scope self, func: impl FnOnce(Self::Result<'scope>));
}
impl<T: 'static> ScopedFetch for &RefCell<T> {
type Result<'r> = &'r T;
fn fetch<'scope>(&'scope self, func: impl FnOnce(Self::Result<'scope>)) {
let data = self.borrow();
func(&data); // Error here
}
}
impl<SF1: ScopedFetch, SF2: ScopedFetch> ScopedFetch for (SF1, SF2) {
type Result<'r> = (SF1::Result<'r>, SF2::Result<'r>);
fn fetch<'scope>(&'scope self, func: impl FnOnce(Self::Result<'scope>)) {
let (sf1, sf2) = self;
sf1.fetch(|data1| {
sf2.fetch(|data2| {
func((data1, data2));
});
});
}
}
error[E0597]: `data` does not live long enough
--> src/lib.rs:14:14
|
12 | fn fetch<'scope>(&'scope self, func: impl FnOnce(Self::Result<'scope>)) {
| ------ lifetime `'scope` defined here
13 | let data = self.borrow();
| ---- binding `data` declared here
14 | func(&data);
| -----^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `data` is borrowed for `'scope`
15 | }
| - `data` dropped here while still borrowed
I tried to generalize the function scope, but then my tuple implementation gives me trouble instead
trait ScopedFetch {
type Result<'r>: 'r;
fn fetch(&self, func: impl FnOnce(Self::Result<'_>));
}
impl<T: 'static> ScopedFetch for &RefCell<T> {
type Result<'r> = &'r T;
fn fetch(&self, func: impl FnOnce(Self::Result<'_>)) {
let data = self.borrow();
func(&data);
}
}
impl<SF1: ScopedFetch, SF2: ScopedFetch> ScopedFetch for (SF1, SF2) {
type Result<'r> = (SF1::Result<'r>, SF2::Result<'r>);
fn fetch(&self, func: impl FnOnce(Self::Result<'_>)) {
let (sf1, sf2) = self;
sf1.fetch(|data1| {
sf2.fetch(|data2| {
func((data1, data2)); // Error here
});
});
}
}
error[E0521]: borrowed data escapes outside of closure
--> src/lib.rs:25:17
|
23 | sf1.fetch(|data1| {
| ----- `data1` declared here, outside of the closure body
24 | sf2.fetch(|data2| {
| ----- `data2` is a reference that is only valid in the closure body
25 | func((data1, data2));
| ^^^^^^^^^^^^^^^^^^^^ `data2` escapes the closure body here
error[E0521]: borrowed data escapes outside of closure
--> src/lib.rs:25:17
|
23 | sf1.fetch(|data1| {
| -----
| |
| `data1` is a reference that is only valid in the closure body
| has type `<SF1 as ScopedFetch>::Result<'1>`
24 | sf2.fetch(|data2| {
25 | func((data1, data2));
| ^^^^^^^^^^^^^^^^^^^^
| |
| `data1` escapes the closure body here
| argument requires that `'1` must outlive `'static`
I feel like what I'm trying to do should be expressable. Solution 1 works with an UnsafeCell, which I eventually plan to do for speed, however I want to make this work safely first. The fact it works for one and not the other is a red flag the UnsafeCell version would be... well... unsafe.
The reborrow trick from kmdreko was almost right, you just have to reborrow both data1
and data2
: (playground)
use std::cell::RefCell;
trait ScopedFetch {
type Result<'r>: 'r;
fn fetch<F>(&self, func: F) where F: for<'a> FnOnce(Self::Result<'a>);
fn reborrow_result<'a: 'b, 'b>(result: Self::Result<'a>) -> Self::Result<'b>;
}
impl<T: 'static> ScopedFetch for &RefCell<T> {
type Result<'r> = &'r T;
fn fetch<F>(&self, func: F) where F: for<'a> FnOnce(Self::Result<'a>) {
let data = self.borrow();
func(&data);
}
fn reborrow_result<'a: 'b, 'b>(result: Self::Result<'a>) -> Self::Result<'b> {
result
}
}
impl<SF1: ScopedFetch, SF2: ScopedFetch> ScopedFetch for (SF1, SF2) {
type Result<'r> = (SF1::Result<'r>, SF2::Result<'r>);
fn fetch<F>(&self, func: F) where F: for<'a> FnOnce(Self::Result<'a>) {
let (sf1, sf2) = self;
sf1.fetch(|data1| {
sf2.fetch(|data2| {
let data1 = SF1::reborrow_result(data1);
let data2 = SF2::reborrow_result(data2);
func((data1, data2));
});
});
}
fn reborrow_result<'a: 'b, 'b>(result: Self::Result<'a>) -> Self::Result<'b> {
(SF1::reborrow_result(result.0), SF2::reborrow_result(result.1))
}
}