wasm-bindgen 中使用 Serde 序列化任意数据并在 Rust 与 JavaScript 之间传递(serde-wasm-bindgen 实战指南)
开发工具【免费下载链接】wasm-bindgenFacilitating high-level interactions between Wasm modules and JavaScript项目地址https://gitcode.com/gh_mirrors/wa/wasm-bindgen点击查看免费下载在 Rust 编写的 WebAssembly 模块与 JavaScript 之间传递数据传统上受限于 wasm ABI 能够表达的类型字符串、数值、布尔值以及JsValue本身。而当你需要跨越边界传递HashMap、嵌套Vec、数组、Option乃至自定义结构体时常规的#[wasm_bindgen]导出就无能为力了。本指南以 guide/src/reference/arbitrary-data-with-serde.md 为核心系统讲解如何借助serde-wasm-bindgen把任意 Rust 数据类型序列化为JsValue传给 JavaScript再把 JavaScript 对象反序列化回 Rust 类型并对比了基于 JSON 的gloo-utils替代方案。读完本文你将掌握两种在 wasm-bindgen 项目中打通复杂数据边界的完整方案并了解它们各自的性能特性与取舍。为什么需要 Serdewasm ABI 的天然限制wasm-bindgen通过生成胶水代码把 Rust 函数导出给 JavaScript但其核心机制是 wasm 的导入/导出函数签名——参数与返回值必须落在有限的 ABI 类型集合内数字、布尔、字符串、JsValue、借用切片等。这意味着像下面这个结构体无法直接通过#[wasm_bindgen]导出到 JavaScriptuse serde::{Serialize, Deserialize}; use std::collections::HashMap; #[derive(Serialize, Deserialize)] pub struct Example { pub field1: HashMapu32, String, pub field2: VecVecf32, pub field3: [f32; 4], }HashMap、嵌套Vec、定长数组等类型都不在 wasm ABI 的可裸传集合中。但这一切型都实现了 Serde 的Serialize/Deserializetrait因此可以借助 Serde 生态将它们编码为 JavaScript 原生数据结构Map、Array、Object、Number、String等再以单个JsValue的形式跨越边界。这就是serde-wasm-bindgen所做的事情。注意参与序列化的自定义结构体不需要标注#[wasm_bindgen]宏它只是一个普通的 Rust 类型仅在 Rust 一侧存在。真正暴露给 JS 的是包装了JsValue转换的导出函数。第一步添加依赖在Cargo.toml中同时加入两个 crateserde本身需要开启derive特性以便使用#[derive(Serialize, Deserialize)]和serde-wasm-bindgen[dependencies] serde { version 1.0, features [derive] } serde-wasm-bindgen 0.4serde的derive特性会引入serde_derive为你的类型生成 trait 实现serde-wasm-bindgen则提供把T: Serialize转换为JsValue、把JsValue转换回T: Deserialize的两个核心函数。第二步为类型派生Serialize与Deserialize给需要跨界的类型加上#[derive(Serialize, Deserialize)]。Serde 的派生宏会对所有字段逐一生成序列化/反序列化逻辑因此每个成员的类型也必须实现这两个 trait。内置类型数字、String、Vec、数组、HashMap、Option、元组等天然满足自定义嵌套类型同样派生即可。以文档中的Example为例它同时包含HashMapu32, String、VecVecf32与[f32; 4]三类ABI 不友好的成员但全部满足 Serde 要求use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] pub struct Example { pub field1: HashMapu32, String, pub field2: VecVecf32, pub field3: [f32; 4], }仓库内亦有同类实践可供参考crates/typescript-tests/src/typescript_type.rs 中TextStyle结构体同时标注了#[wasm_bindgen]与#[derive(Serialize, Deserialize)]通过serde_wasm_bindgen::from_value把从 JavaScript 传入的接口对象直接转换成 Rust 结构。第三步用serde_wasm_bindgen::to_value发送到 JavaScriptRust 侧导出函数构造数据后调用serde_wasm_bindgen::to_value(example)得到ResultJsValue, Errorunwrap()后把JsValue作为返回值交给 JavaScriptuse wasm_bindgen::prelude::*; use std::collections::HashMap; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] pub struct Example { pub field1: HashMapu32, String, pub field2: VecVecf32, pub field3: [f32; 4], } #[wasm_bindgen] pub fn send_example_to_js() - JsValue { let mut field1 HashMap::new(); field1.insert(0, String::from(ex)); let example Example { field1, field2: vec![vec![1., 2.], vec![3., 4.]], field3: [1., 2., 3., 4.] }; serde_wasm_bindgen::to_value(example).unwrap() }调用成功后JavaScript 拿到的JsValue是一个普通对象其中field1是Map、field2是成员为数字数组的Array、field3是数字Array。序列化失败的场景例如类型不可序列化会返回Err此时需要根据具体错误处理而不是盲目unwrap。第四步用serde_wasm_bindgen::from_value从 JavaScript 接收反向路径同样简单导出函数接收一个JsValue参数用serde_wasm_bindgen::from_value(val)反序列化回目标类型#[wasm_bindgen] pub fn receive_example_from_js(val: JsValue) { let example: Example serde_wasm_bindgen::from_value(val).unwrap(); // ... 使用 example }这里的类型注解是必须的因为from_value是泛型函数需要借助目标类型推断Deserialize实现。如果 JavaScript 传入的值结构与目标类型不匹配例如缺少字段、类型不符from_value会返回Err。仓库测试中可以看到完整的出/入双向验证在 tests/wasm/js_objects.rs 的serde测试feature serde-serialize门控里Rust 侧先构造含OptionSerdeBar、嵌套结构体的SerdeFoo序列化为JsValue交予 JavaScript 侧校验见 tests/wasm/js_objects.jsverify_serde用deepStrictEqual断言收到的对象形状再接收 JS 返回的对象反序列化回SerdeFoo并逐字段断言。这个用例同时印证了Option、嵌套结构体以及undefined无法反序列化为i32ok()为None等边界行为。JavaScript 侧用法拿到对象后自由操作再回传由于serde-wasm-bindgen生成的是 JavaScript 原生数据结构JavaScript 侧可以像操作普通对象一样直接读取、修改再传回 wasmimport { send_example_to_js, receive_example_from_js } from example; // 从 wasm 获取 example 对象。 let example send_example_to_js(); // 在 VecVecf32 末尾追加一个 Vec 元素。 example.field2.push([5, 6]); // 把修改后的对象传回 wasm。 receive_example_from_js(example);注意这里field2是真正的Array所以可以直接push——修改后的数据回传 wasm 时from_value会按VecVecf32重新解析。整个往返无需任何额外桥接代码。另一种方案基于 JSON 的gloo-utils扩展serde-wasm-bindgen直接逐个操作 JavaScript 值因此在 Rust 与 JavaScript 之间会产生大量来回调用某些场景下可能偏慢。替代思路是把值先序列化成 JSON 字符串在另一端解析。浏览器内置的 JSON 实现通常很快所以这种方式在部分场景下能超过serde-wasm-bindgen的性能但它只支持可被 JSON 表达的类型会丢掉serde-wasm-bindgen支持的一些重要类型例如Map、Set以及 ArrayBuffer二进制缓冲区等。该方案由gloo_utils的JsValueSerdeExt扩展 trait 提供在Cargo.toml中开启其serde特性[dependencies] gloo-utils { version 0.1, features [serde] }Rust 侧用法几乎与serde-wasm-bindgen同构只是把to_value/from_value换成扩展方法use gloo_utils::format::JsValueSerdeExt; #[wasm_bindgen] pub fn send_example_to_js() - JsValue { let mut field1 HashMap::new(); field1.insert(0, String::from(ex)); let example Example { field1, field2: vec![vec![1., 2.], vec![3., 4.]], field3: [1., 2., 3., 4.] }; JsValue::from_serde(example).unwrap() } #[wasm_bindgen] pub fn receive_example_from_js(val: JsValue) { let example: Example val.into_serde().unwrap(); // ... 使用 example }gloo-utils的 JSON 方案在 wasm-bindgen 仓库中也有长期测试覆盖tests/wasm/js_objects.rs中的serde测试受serde-serializefeature 门控使用的正是JsValue::from_serde与JsValue::into_serde而 tests/wasm/js_objects.js 以deepStrictEqual校验了往返对象的一致性。这为读者提供了可直接对照的参考实现。两种方案如何取舍需要澄清的是JSON 方案并非永远更快——它的实际速度介于serde-wasm-bindgen的0.2x 到 2x之间具体取决于 JavaScript 运行时以及所传递的值本身同时 JSON 方案通常带来更大的代码体积需要携带serde_json相关的序列化/反序列化逻辑。结论是不要凭直觉选型请针对自己的数据结构、目标浏览器/运行时分别做性能剖析profile。若你的数据中包含Map、Set、ArrayBuffer 等 JSON 无法表达的类型则只能选择serde-wasm-bindgen。历史背景为什么from_serde/into_serde不在 wasm-bindgen 里了在 wasm-bindgen 的早期版本中基于 JSON 的 Serde 支持JsValue::from_serde与JsValue::into_serde曾内置在 wasm-bindgen 自身。但这样做强制引入了对serde_json的依赖进而带来一个现实问题在serde_json的某些特性与其它 crate 的特性组合下serde_json会与wasm-bindgen形成循环依赖circular dependency这在 Rust 中是非法的导致用户代码编译失败。为此这些方法被抽取到gloo-utils中以扩展 traitJsValueSerdeExt的形式提供wasm-bindgen 内的原始方法则被弃用deprecated。这也是为什么文档与上述测试代码中会见到#[allow(deprecated)]标注参见 tests/wasm/js_objects.rs——仓库自身的回归测试仍在验证这一遗留 API但新代码应当优先使用gloo-utils或serde-wasm-bindgen。仓库中的更多实践参考crates/typescript-tests/src/typescript_type.rs用serde_wasm_bindgen::from_value把 TypeScript 接口对象直接反序列化为 Rust 结构体的构造器模式crates/typescript-tests/Cargo.toml实际工程中serdeserde-wasm-bindgen的依赖写法examples/raytrace-parallel/src/lib.rs在并行光线追踪示例中从 JavaScript 传入的对象经serde_wasm_bindgen::from_value还原为 Rust 数据结构tests/wasm/js_objects.rs 与 tests/wasm/js_objects.js双向序列化/反序列化的完整回归测试。小结在 wasm-bindgen 项目中传递任意复杂数据有两条成熟路线serde-wasm-bindgen直接操作 JavaScript 值支持Map、Set、ArrayBuffer 等 JSON 之外的丰富类型适合数据形状复杂、类型多样或需要二进制数据的场景gloo-utils的 JSON 方案则借助浏览器高效的 JSON 解析在简单 JSON 类型上有望获得更优速度与更小的运行时开销但类型覆盖面窄、代码体积更大。无论选择哪条路线都建议先用真实数据在自己的目标运行时上做基准测试再决定最终方案。赞分享开发工具【免费下载链接】wasm-bindgenFacilitating high-level interactions between Wasm modules and JavaScript项目地址https://gitcode.com/gh_mirrors/wa/wasm-bindgen点击查看免费下载相关推荐为什么CodeGeeX2-6B在6种编程语言上全面超越150亿参数模型为什么CodeGeeX2 6B在6种编程语言上全面超越150亿参数模型 CodeGeeX2 6B是基于ChatGLM2架构开发的强大多语言代码生成模型仅需60serde-wasm-bindgen 项目教程serde wasm bindgen 项目教程 1、项目介绍 serde wasm bindgen 是一个开源项目旨在简化在 WebAssembly WasmSerde-Wasm-Bindgen Rust 与 Web 的无缝衔接Serde Wasm Bindgen Rust 与 Web 的无缝衔接 项目介绍 Serde Wasm Bindgen 是一个强大的工具它允许Rust开发者上一篇Reference 项目 Lua 5.4 速查表从基础语法到表、元表与文件 IO 的完整实战指南下一篇tiny11builder:一条脚本把 6GB 的 Windows 11 安装映像压成精简版创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考