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

如何将可变Rust结构体传入JS回调并保留Wasm中的修改?

如何通过wasm-bindgen让JavaScript直接操作Rust结构体并同步修改原对象

问题背景

使用wasm_bindgen传递包含Vec等不可Copy类型的Rust结构体到JavaScript回调时,Serde序列化仅能传递对象副本,无法调用Rust定义的方法,也无法同步修改Rust内存中的原对象。直接传递结构体需克隆副本,导致JS修改无法同步回原对象;手动复制字段会触发借用检查错误,直接调用原对象方法则会出现别名不安全问题。

解决方案

方案一:安全共享可变引用(推荐)

利用Rc<RefCell<T>>在单线程Wasm环境下实现结构体的共享可变访问,通过包装类将引用暴露给JS,确保所有修改直接作用于原对象。

Rust代码实现

use wasm_bindgen::prelude::*;
use std::rc::Rc;
use std::cell::RefCell;

#[wasm_bindgen]
#[derive(Debug)]
pub struct SomeStruct {
    data: Vec<i32>,
}

#[wasm_bindgen]
impl SomeStruct {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        SomeStruct { data: vec![1, 2, 3] }
    }

    pub fn add_data(&mut self, value: i32) {
        self.data.push(value);
    }

    pub fn get_data(&self) -> JsValue {
        JsValue::from_serde(&self.data).unwrap()
    }
}

// 用于JS持有的结构体引用包装类
#[wasm_bindgen]
pub struct SomeStructHandle {
    inner: Rc<RefCell<SomeStruct>>,
}

#[wasm_bindgen]
impl SomeStructHandle {
    pub fn from_struct(s: SomeStruct) -> Self {
        SomeStructHandle {
            inner: Rc::new(RefCell::new(s)),
        }
    }

    // 暴露修改方法,内部通过RefCell获取可变引用
    pub fn add_data(&self, value: i32) {
        self.inner.borrow_mut().add_data(value);
    }

    // 暴露数据获取方法
    pub fn get_data(&self) -> JsValue {
        self.inner.borrow().get_data()
    }
}

// 示例:将结构体引用传递给JS回调
#[wasm_bindgen]
pub fn run_with_struct(callback: &js_sys::Function) {
    let struct_instance = SomeStruct::new();
    let handle = SomeStructHandle::from_struct(struct_instance);

    // 将handle作为参数传入JS回调
    callback.call1(&JsValue::null(), &JsValue::from(handle)).unwrap();

    // 验证原对象已被修改
    let final_data = handle.inner.borrow().get_data();
    web_sys::console::log_1(&final_data);
}

JavaScript代码调用

import init, { run_with_struct } from './pkg/your_wasm_module.js';

async function main() {
    await init();

    run_with_struct((handle) => {
        // 调用Rust方法修改原对象
        handle.add_data(4);
        console.log("JS中获取的数据:", handle.get_data());
    });
}

main();

方案说明

  • Rc提供引用计数,确保结构体在JS持有引用期间不会被释放;RefCell在运行时检查借用规则,避免编译时的借用冲突
  • JS持有的SomeStructHandle是原结构体的引用,所有方法调用直接作用于原对象,修改自动同步回Rust
  • 无需克隆结构体,避免了副本带来的数据不一致问题

方案二:Unsafe原始指针(性能导向)

如果追求极致性能或有特殊需求,可使用原始指针直接传递结构体地址,但需手动管理内存安全,避免悬空指针和内存泄漏。

Rust代码实现

use wasm_bindgen::prelude::*;
use std::boxed::Box;

#[wasm_bindgen]
#[derive(Debug)]
pub struct SomeStruct {
    data: Vec<i32>,
}

#[wasm_bindgen]
impl SomeStruct {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        SomeStruct { data: vec![1, 2, 3] }
    }

    pub fn add_data(&mut self, value: i32) {
        self.data.push(value);
    }

    pub fn get_data(&self) -> JsValue {
        JsValue::from_serde(&self.data).unwrap()
    }
}

// 创建结构体并返回原始指针
#[wasm_bindgen]
pub fn create_struct_ptr() -> *mut SomeStruct {
    Box::into_raw(Box::new(SomeStruct::new()))
}

// 通过指针调用修改方法
#[wasm_bindgen]
pub fn add_data_to_ptr(ptr: *mut SomeStruct, value: i32) {
    unsafe {
        (*ptr).add_data(value);
    }
}

// 通过指针获取数据
#[wasm_bindgen]
pub fn get_data_from_ptr(ptr: *mut SomeStruct) -> JsValue {
    unsafe {
        (*ptr).get_data()
    }
}

// 释放结构体内存,避免泄漏
#[wasm_bindgen]
pub fn drop_struct_ptr(ptr: *mut SomeStruct) {
    unsafe {
        Box::from_raw(ptr);
    }
}

// 将指针传递给JS回调
#[wasm_bindgen]
pub fn run_with_struct_ptr(callback: &js_sys::Function) {
    let ptr = create_struct_ptr();

    // 将指针转为JsValue传入回调
    let ptr_js = JsValue::from(ptr as u32);
    callback.call1(&JsValue::null(), &ptr_js).unwrap();

    // 验证修改
    let final_data = get_data_from_ptr(ptr);
    web_sys::console::log_1(&final_data);

    // 必须手动释放内存
    drop_struct_ptr(ptr);
}

JavaScript代码调用

import init, { run_with_struct_ptr, add_data_to_ptr, get_data_from_ptr } from './pkg/your_wasm_module.js';

async function main() {
    await init();

    run_with_struct_ptr((ptr) => {
        add_data_to_ptr(ptr, 4);
        console.log("JS中获取的数据:", get_data_from_ptr(ptr));
    });
}

main();

方案注意点

  • 必须手动调用drop_struct_ptr释放内存,否则会造成内存泄漏
  • 确保指针在使用期间结构体未被释放,否则会出现悬空指针导致未定义行为
  • 该方式跳过Rust的借用检查,仅推荐在单线程Wasm环境下使用,多线程场景需额外加锁

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 03:10:25