Rust如何获取对象RefCell引用及其内部字段的Rc引用
问题背景
开发WebAssembly相关功能时需要实现两类回调逻辑:
- 定时器回调:调用目标对象的可变方法
- requestAnimationFrame回调:调用目标对象内部字段的方法,该字段通过对象提供的
ref_to_item()方法获取,由于该方法调用开销极高,无法在回调内部重复调用获取字段
尝试实现时反复出现编译错误,最常见报错为creates a temporary which is freed while still in use,需要确认该需求的可实现方式。
初始问题代码:
let mut top = Top { item: Item {} }; let top_rc_refcell = Rc::new(RefCell::new(top)); let top_rc_1 = Rc::clone(&top_rc_refcell); let box_1 = Box::new(move || { top_rc_1.borrow_mut().some_mut(); }); let top_rc_2 = Rc::clone(&top_rc_refcell); //ref_to_item is costly so calling it from within callback is not an option let item_rc = Rc::new(&top_rc_2.borrow().ref_to_item()); let item_rc_in_box = Rc::clone(&item_rc); let box_2 = Box::new(move || { item_rc_in_box.some(); }); (box_1)(); (box_2)();
对应编译报错:
error[E0716]: temporary value dropped while borrowed --> src/lib.rs:46:28 | 46 | let item_rc = Rc::new(&top_rc_2.borrow().ref_to_item()); | ^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary which is freed while still in use 47 | 48 | let item_rc_in_box = Rc::clone(&item_rc); | -------- borrow later used here | = note: consider using a `let` binding to create a longer lived value error[E0716]: temporary value dropped while borrowed --> src/lib.rs:46:28 | 46 | let item_rc = Rc::new(&top_rc_2.borrow().ref_to_item()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary which is freed while still in use 47 | 48 | let item_rc_in_box = Rc::clone(&item_rc); | -------- borrow later used here | = note: consider using a `let` binding to create a longer lived value
错误原因
报错核心是生命周期不匹配:
top_rc_2.borrow()会返回一个临时的Ref<'_, Top>借用守卫,该守卫的生命周期仅存在于当前语句,语句执行结束就会被自动释放ref_to_item()返回的Item引用生命周期依附于前面的Ref守卫,守卫释放后该引用就会变成悬垂引用- 代码尝试将这个短生命周期的引用包装到
Rc中供回调长期持有,直接违反Rust的借用规则,被编译器拦截。
额外需要注意:如果尝试通过延长Ref守卫生命周期的方式让Item引用长期存活,会和定时器回调的borrow_mut()产生冲突——RefCell不允许同时存在活跃的不可变借用和可变借用,会触发运行时panic,不适用于两个回调需要长期存活的场景。
可行实现方案
该需求完全可以实现,最稳妥无冲突的方案是拆分Item的共享所有权,从根源避开生命周期和借用冲突问题:
- 将Top内部的
item字段用Rc<Item>包裹,让Item具备独立的共享所有权 - 改造
ref_to_item()方法,直接返回克隆的Rc<Item>,该操作仅会增加引用计数,开销极低,完全替代原有的高开销逻辑 - 初始化rAF回调前提前调用一次
ref_to_item()拿到Item的Rc克隆,移入回调即可,无需在回调内重复执行高开销逻辑,也不会和Top的可变借用产生任何冲突
可运行的实现代码:
use std::{cell::RefCell, rc::Rc}; struct Item; impl Item { fn some(&self) { // Item对应逻辑 } } struct Top { // 用Rc包裹Item,支持共享所有权 item: Rc<Item>, } impl Top { fn some_mut(&mut self) { // Top的可变方法逻辑 } // 改造后该方法仅克隆Rc,开销极低 fn ref_to_item(&self) -> Rc<Item> { self.item.clone() } } fn main() { let top = Top { item: Rc::new(Item {}), }; let top_rc_refcell = Rc::new(RefCell::new(top)); // 定时器回调:持有Top的Rc,调用时可变借用 let top_rc_1 = Rc::clone(&top_rc_refcell); let box_1 = Box::new(move || { top_rc_1.borrow_mut().some_mut(); }); // rAF回调:提前拿到Item的Rc克隆,不持有Top的借用,无冲突 let item_rc = top_rc_refcell.borrow().ref_to_item(); let box_2 = Box::new(move || { item_rc.some(); }); // 执行回调无编译错误、无运行时冲突 box_1(); box_2(); }
该方案的优势:
- 完全避免悬垂引用问题,符合Rust所有权规则
- 两个回调的调用顺序没有限制,不会触发RefCell借用冲突
- 仅在初始化回调时执行一次原高开销的
ref_to_item()相关逻辑,回调执行时仅做极低开销的Rc方法调用,满足性能要求
注:如果ref_to_item()是外部依赖提供的方法无法改造,且Item类型支持克隆(克隆开销远低于ref_to_item()调用开销),可以在首次调用ref_to_item()拿到引用后直接克隆Item实例,包装为Rc<Item>移入回调,同样可以实现需求。
内容的提问来源于stack exchange,提问作者user656449
相关产品推荐
相关产品推荐

