如何从仅追加的Arena生成'static静态引用?
问题描述
我在开发编译器应用,需要在程序执行期间创建多种循环数据结构,它们的生命周期持续到编译结束,同时满足:
- 无需考虑多线程场景
- 仅需追加数据,无需删除或垃圾回收
- 仅需数据的不可变引用
原本想用Arena,但需要在所有函数中传递Arena,开销较大。于是用thread_local!定义全局数据,封装数组索引自定义类型并实现Deref trait,代码如下:
use std::cell::RefCell; enum Floop { CaseA, CaseB, CaseC(FloopRef), CaseD(FloopRef), CaseE(Vec<FloopRef>), } thread_local! { static FLOOP_ARRAY: RefCell<Vec<Box<Floop>>> = RefCell::new(Vec::new()); } pub struct FloopRef(usize); impl std::ops::Deref for FloopRef { type Target = Floop; fn deref(&self) -> &Self::Target { return FLOOP_ARRAY.with(|floops| &floops.borrow()[self.0]); } } pub fn main() { // initialize some data FLOOP_ARRAY.with(|floops| { floops.borrow_mut().push(Box::new(Floop::CaseA)); let idx = floops.borrow_mut().len(); floops.borrow_mut().push(Box::new(Floop::CaseC(FloopRef(idx)))); }); }
但遇到生命周期错误:
error: lifetime may not live long enough --> src/main.rs:20:36 | 20 | return FLOOP_ARRAY.with(|floops| &floops.borrow()[self.0]); | ------- ^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2` | | | | | return type of closure is &'2 Box<Floop> | has type `&'1 RefCell<Vec<Box<Floop>>>` error[E0515]: cannot return value referencing temporary value --> src/main.rs:20:36 | 20 | return FLOOP_ARRAY.with(|floops| &floops.borrow()[self.0]); | ^---------------^^^^^^^^ | || | |temporary value created here | returns a value referencing data owned by the current function
我想告诉编译器:我保证不会删除数组元素、不会跨线程共享数据,且数组会存活到程序结束,因此可以返回&'static类型的Floop引用,但Rust认为这不安全。请问是否有Rust辅助库可实现该需求?或者即使保证仅追加、单线程使用,仍存在安全漏洞?
解答
报错原因
当前代码的核心问题是:floops.borrow()返回的Ref是闭包内的临时值,生命周期仅限于with闭包内部。当闭包执行完毕,Ref会被销毁,此时返回的引用就会变成悬垂引用。此外,Rust无法确认你后续不会通过RefCell修改数组——比如Vec扩容时虽不会影响Box的堆地址,但如果后续误操作删除元素,或者直接存储非Box类型导致元素移动,都会让之前的引用失效,所以借用检查器拒绝这种行为。
可行实现方案
1. 手动使用static+UnsafeCell(零依赖)
由于你满足单线程、仅追加、数据存活到程序结束的条件,可以用static结合UnsafeCell手动实现安全的全局Arena,关键是利用Box的堆地址不会随Vec扩容而改变的特性:
use std::cell::UnsafeCell; use std::sync::OnceLock; #[derive(Debug)] enum Floop { CaseA, CaseB, CaseC(FloopRef), CaseD(FloopRef), CaseE(Vec<FloopRef>), } #[derive(Debug)] pub struct FloopRef(usize); static FLOOP_ARRAY: OnceLock<UnsafeCell<Vec<Box<Floop>>>> = OnceLock::new(); impl FloopRef { fn get(&self) -> &'static Floop { // 初始化全局数组,确保只执行一次 let array = FLOOP_ARRAY.get_or_init(|| UnsafeCell::new(Vec::new())); // 手动保证安全:单线程无并发修改,仅追加操作不会导致Box内部引用失效 unsafe { &*(*array.get())[self.0] } } } impl std::ops::Deref for FloopRef { type Target = Floop; fn deref(&self) -> &Self::Target { self.get() } } pub fn main() { let array = FLOOP_ARRAY.get_or_init(|| UnsafeCell::new(Vec::new())); unsafe { (*array.get()).push(Box::new(Floop::CaseA)); // 注意:push后len为1,索引是0,原代码的idx计算错误 let idx = (*array.get()).len() - 1; (*array.get()).push(Box::new(Floop::CaseC(FloopRef(idx)))); } let ref_c = FloopRef(1); println!("{:?}", ref_c); // 输出CaseC(FloopRef(0)) }
2. 使用现成的Arena库(无需unsafe)
如果你不想编写unsafe代码,bumpalo是专门针对单线程、仅追加场景的Arena库,用OnceLock全局初始化后无需传递,完全符合需求:
use bumpalo::Bump; use std::sync::OnceLock; #[derive(Debug)] enum Floop<'a> { CaseA, CaseB, CaseC(FloopRef<'a>), CaseD(FloopRef<'a>), CaseE(Vec<FloopRef<'a>>), } #[derive(Debug)] pub struct FloopRef<'a>(&'a Floop<'a>); static ARENA: OnceLock<Bump> = OnceLock::new(); impl<'a> std::ops::Deref for FloopRef<'a> { type Target = Floop<'a>; fn deref(&self) -> &Self::Target { self.0 } } pub fn main() { let arena = ARENA.get_or_init(Bump::new); let case_a = arena.alloc(Floop::CaseA); let case_c = arena.alloc(Floop::CaseC(FloopRef(case_a))); println!("{:?}", case_c); // 输出CaseC(FloopRef(CaseA)) }
安全风险分析
即使你保证仅追加、单线程,直接用RefCell返回&'static引用仍存在潜在风险:
- 如果后续代码误调用
Vec的删除方法(如pop、remove),会导致对应索引的Box被销毁,之前的引用变成悬垂引用。 - 若存储的不是
Box<Floop>而是直接Floop,Vec扩容时会移动元素,所有旧引用都会失效。 RefCell无法限制后续的borrow_mut()操作,一旦有代码修改数组结构,引用安全性就无法保证。
内容的提问来源于stack exchange,提问作者Christopher Rybicki

