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

如何创建捕获Rc<RefCell<_>>不移动所有权的wasm_bindgen Closure

问题根因

你的代码报错核心是两个认知偏差:

  • Closure::wrap要求传入的闭包必须满足'static生命周期——因为JS回调的存活时间不受Rust栈帧控制,闭包不能借用任何栈上的临时数据,否则函数返回后栈帧销毁,借用就会悬空。
  • 你虽然用Rc<RefCell<_>>创建了共享状态,但闭包是通过&mut ||的形式借用了外层栈上的data变量本身,并没有把Rc的所有权移进闭包。当run函数执行结束,栈上的data被drop,闭包持有的引用就成了野指针,和你用不用Rc没有关系。
修复方式

核心逻辑是:为闭包克隆一份Rc引用,通过move闭包把这份Rc的所有权完全移进闭包内部,让闭包不依赖任何外层栈上的变量,自然满足'static要求。
修复后的可运行代码:

use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::console;

macro_rules! console_log {
    ($($t:tt)*) => {
        console::log_1(&format_args!($($t)*).to_string().into())
    };
}

#[wasm_bindgen(start)]
pub fn run() {
    // 初始化全局共享的游戏状态
    let game_state = Rc::new(RefCell::new(vec![0]));
    // 克隆一份Rc,仅增加引用计数,不会复制底层数据
    let state_for_closure = Rc::clone(&game_state);

    // 用move关键字强制闭包拿走捕获变量的所有权,不要加&mut借用闭包
    let callback = Closure::wrap(Box::new(move || {
        state_for_closure.borrow_mut().push(0);
        console_log!("{:?}", state_for_closure.borrow());
    }) as Box<dyn FnMut()>);

    // 实际使用时把回调绑定到对应的JS事件上,示例为绑定window点击事件
    let window = web_sys::window().unwrap();
    window.set_onclick(Some(callback.as_ref().unchecked_ref()));

    // 如果需要回调永久存活(比如绑定全局生命周期的事件)再调用forget,否则建议把callback实例存到全局状态中,避免内存泄漏
    callback.forget();
}
扩展说明
  • 如果需要在多个不同的JS回调中共享同一份游戏状态,只需要在创建每个闭包前都调用一次Rc::clone(&game_state),把克隆出的Rc移进对应闭包即可,所有闭包访问修改的都是同一份底层数据。
  • Rc<RefCell<T>>仅适用于WASM主线程单线程场景,如果后续需要在WebWorker多线程环境使用,替换为Arc<Mutex<T>>/Arc<RwLock<T>>即可,逻辑完全一致。
  • 不要滥用closure.forget():它会直接泄漏闭包内存,适合全局生命周期的事件回调;如果回调是绑定到会被销毁的DOM节点上,建议把Closure实例存到和DOM同生命周期的Rust状态中,节点销毁时手动释放闭包,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Michael Francis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 13:33:23