ECS系统中实现ScopedFetch trait时的RefCell借用生命周期错误求助
ECS系统中实现ScopedFetch trait时的RefCell借用生命周期错误求助
我正在开发一个ECS系统,在遍历查询对的过程中,需要在执行系统的有限作用域内获取组件的直接引用。为了实现这个功能,我尝试定义一个ScopedFetch trait:它的目标是在限定的作用域内返回组件的引用,同时支持用嵌套元组来组合任意数量的组件。
但现在我卡在了Rust的借用生命周期检查上——试了两个方案,各自都有不同的编译错误,想请教大家怎么解决这个问题。
解决方案1:固定作用域生命周期
这个方案里我为fetch函数指定了'scope生命周期,但单组件的实现就报错了:
use std::cell::RefCell; 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); // 错误发生在这里 } } 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
我理解这里的问题:data是fetch函数内部创建的Ref<T>,它的生命周期只到函数结束,但func要求接收一个生命周期为'scope的引用——显然data活不了那么久,所以编译器报错。
解决方案2:用匿名生命周期泛化作用域
后来我试着把fetch的生命周期改成匿名的'_,希望让编译器自动推导作用域,但这次元组的实现又报错了:
use std::cell::RefCell; 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[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`
这里的问题我大概能猜到:嵌套调用fetch时,data1和data2的生命周期只在各自的闭包内有效,但我试图把它们组合起来传给外层的func,编译器认为这些引用逃逸出了它们的有效作用域。
我感觉这个需求应该是能通过安全代码实现的——如果用UnsafeCell代替RefCell,解决方案1能正常编译,我之后也确实打算为了性能用UnsafeCell,但现在想先搞懂安全写法怎么实现。毕竟“不安全代码能跑,安全写法却不行”这个情况,让我怀疑UnsafeCell的版本可能藏着未定义行为的风险,所以想先把安全的版本搞对,再考虑性能优化。
有没有大佬能指点一下,这个ScopedFetch的安全实现应该怎么写?
内容来源于stack exchange
相关产品推荐
相关产品推荐

