You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 13:45:36