不使用wasm_bindgen时Rust WASM向JS传字符串返回undefined问题
问题分析与解决
核心问题1:WASM不支持直接返回元组
标准WebAssembly目前仅支持返回单个数值类型(i32、i64、f32、f64),你写的Rust函数返回(u8, usize)元组,编译后不符合WASM的函数返回规范,JS无法解析这种非标准返回值,因此得到undefined。
核心问题2:字符串内存被提前释放+指针取错
- 你用
ptr::addr_of!(s)取的是RustString结构体自身的地址,而非字符串底层字节数组的指针。String在Rust里是包含指针、长度、容量的三元结构体,你需要的是它指向的字节数组指针,应该用s.as_ptr()。 - 函数内的
s是局部变量,函数执行完毕后Rust会自动释放它的内存,就算拿到正确指针,后续访问也是野指针,内存已被回收。
修正方案
步骤1:调整Rust代码,解决内存与返回值问题
我们可以把字符串转为静态内存(避免被自动释放),通过两个函数分别返回指针和长度,逻辑更直观:
use std::ffi::CString; // 全局变量存储字符串指针,供后续获取 static mut GREET_STR: *const u8 = std::ptr::null(); #[no_mangle] pub fn greet(name: &str) -> usize { let s = format!("Hello, {}!", name); // 转成CString确保末尾带null终止符(可选,按需使用),通过into_raw让内存脱离Rust的自动释放机制 let c_str = CString::new(s).unwrap(); let ptr = c_str.into_raw(); unsafe { GREET_STR = ptr; } // 返回字符串长度(不含null终止符) s.len() } #[no_mangle] pub fn get_greet_ptr() -> usize { unsafe { GREET_STR as usize } } // 可选:提供内存释放函数,避免内存泄漏 #[no_mangle] pub fn free_greet_str() { unsafe { if !GREET_STR.is_null() { // 将原始指针转回CString,触发drop释放内存 let _ = CString::from_raw(GREET_STR as *mut u8); GREET_STR = std::ptr::null(); } } }
步骤2:JS端读取WASM内存
通过wasm.memory获取WASM的内存缓冲区,用Uint8Array读取指定指针和长度的字节,再转为JS字符串:
let wasm = wasmModule.instance.exports; // 调用greet获取字符串长度,再获取指针 const strLen = wasm.greet("WebAssembly"); const strPtr = wasm.get_greet_ptr(); // 从WASM内存中截取对应字节 const memory = new Uint8Array(wasm.memory.buffer); const strBytes = memory.subarray(strPtr, strPtr + strLen); // 解码为JS字符串 const greeting = new TextDecoder().decode(strBytes); console.log(greeting); // 输出 "Hello, WebAssembly!" // 使用完毕后手动释放内存,避免泄漏 wasm.free_greet_str();
额外说明
- 32位/64位WASM下,
usize都能被JS正确识别为数值类型。 - 用
CString::into_raw或Box::leak的目的是让内存脱离Rust的自动释放逻辑,必须手动调用释放函数,否则会造成WASM内存泄漏。 - 也可以把指针和长度打包成一个i64返回(比如指针存低32位、长度存高32位),再在JS端拆分,但双函数的方式更易理解。
内容的提问来源于stack exchange,提问作者Jacob Runge
相关产品推荐
相关产品推荐

