本文将手把手介绍如何将rust编写的函数打包为wasm供js使用,并解释其关键配置和实践中的常见问题。
5分钟快速上手
1. 环境准备
rust和cargo
rust环境安装教程参考如下:
可执行rustc --version和cargo --version验证安装,如果没问题会有如下类似输出
rustc 1.92.0 (ded5c06cf 2025-12-08)
cargo 1.92.0 (344c4567c 2025-10-21)
wasm编译目标和 wasm-pack
执行以下命令;
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
2. rust lib项目创建
cargo相当于js的npm,可以用cargo创建一个lib:
cargo new test-rust-lib --lib
--lib是生成库,默认是--bin,也就是生成项目。两者的区别是前者会在src下生成lib.rs,后者是mian.rs。
3. 修改Cargo.toml
修改Cargo.toml,添加依赖和lib配置
[package]
name = "test-rust-lib"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
4.编写rust代码
这里用项目默认的add为例,使用 wasm_bindgen 宏标记要导出的函数:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
5.编译和发布
执行以下命令即可在当前项目的pkg文件中生成wasm文件
wasm-pack build --target web
生成的文件里自带package.json,可以直接发布npm包
npm login
npm publish
本地测试
方便起见,可以直接在项目的pkg目录中创建一个html进行测试:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<h1>curve lib test</h1>
<script type="module">
//加载并执行...
</script>
</body>
</html>
加载打包出来的test_rust_lib.js:
//加载并执行...
import init, { add } from "./test_rust_lib.js";//或npm包
async function run() {
const res = await init();
console.log(add(1n, 2n));
}
run();
注意因为rust中定义的类型是u64,js中对应的是BigIng
rust wasm进阶
本节将详细解释wasm打包中的关键角色。
编译目标 wasm32-unknown-unknown
如果你是第一次看到wasm32-unknown-unknown可能有点懵逼,
wasm32-unknown-unknown 是 Rust 的编译目标(target triple) ,代表你要把代码编译成 WebAssembly (WASM) 格式的二进制文件。
Rust 的 target 三元组(triple)一般形如:
<架构>-<厂商>-<操作系统>
对于 wasm32-unknown-unknown 来说:
| 部分 | 含义 | 说明 |
|---|---|---|
wasm32 | 架构 | 32 位 WebAssembly 指令集(WASM 目前只定义了 32 位) |
unknown | 厂商 | 没有特定厂商(WebAssembly 是开放标准) |
unknown | 操作系统 | 没有底层 OS(WASM 在虚拟机环境中执行) |
换句话说,它的意思是:
“编译为 32 位 WebAssembly 目标,独立于具体厂商或操作系统的环境。”
下面是一些常见的target和适用环境
| Target | 位数 | 环境 | 标准库 | 说明 / 场景 |
|---|---|---|---|---|
wasm32-unknown-unknown | 32 位 | 浏览器 / 纯 Web 环境 | ❌ 无 std | ✅ 浏览器最常用目标,配合 wasm-bindgen |
wasm64-unknown-unknown | 64 位 | 浏览器(未来标准) | ❌ 无 std | 🧪 实验中,支持 64 位寻址(>4GB 内存) |
wasm32-wasi | 32 位 | 服务端 / CLI | ✅ 有 std | ✅ 支持文件、环境变量、socket |
wasm64-wasi | 64 位 | 服务端 / CLI | ✅ 有 std | 🧪 新兴,WASI 64-bit 支持大内存服务 |
wasm32-unknown-emscripten | 32 位 | 浏览器 (旧方案) | ✅ 有 std | ⚠️ 已过时,体积大,依赖 JS runtime |
如果是浏览器加载,无脑wasm32-unknown-unknown
crate-type
上文在toml文件中有如下配置:
[lib]
crate-type = ["cdylib", "rlib"]
可能有人会问:什么是crate-type
在 Rust 中,一个 crate(包)可以被编译成多种形式:
- 可执行文件(
bin) - 静态库(
staticlib) - 动态库(
dylib/cdylib) - Rust 库文件(
rlib) - WebAssembly 模块(通过
cdylib或wasm32-*目标)
crate-type 就是告诉编译器 “我要生成哪种类型的输出”。
主要类型说明:
| 类型 | 说明 | 适用场景 |
|---|---|---|
rlib | Rust 专用静态库格式(默认) | ✅ 供其他 Rust crate 依赖 |
cdylib | C-compatible 动态库(推荐用于 FFI / WASM) | ✅ 用于导出函数给其他语言(C, Python, JS等) |
dylib | Rust 动态库(带 Rust 元数据) | ⚠️ 较少使用,只能被 Rust 加载 |
staticlib | 纯静态库(.a) | ✅ 方便嵌入 C/C++ 程序 |
bin | 可执行程序 | 用于 CLI 应用 |
自定义类型的导出问题
如果导出的函数里有自定义类型,比如下面代码,就会提示
the trait bound
CurveFitConfig: FromWasmAbiis not satisfied”
比如下面这段代码:
#[wasm_bindgen]
pub fn curve_fitting(x: f64, config: CurveFitConfig) -> Option<f64> {
...
}
这里最佳的解决办法是JsValue和serde-wasm-bindgen,保持原函数不变,另导出一个给js调用的函数。
use serde_wasm_bindgen::from_value;
#[wasm_bindgen]
pub fn curve_fitting_wasm(x: f64, config: JsValue) -> Option<f64> {
let cfg: CurveFitConfig = from_value(config).ok()?;
curve_fitting(x, cfg)
}
对相应的类型增加序列化宏
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct CurveFitConfig {
...
}
wasm导出函数的测试问题
wasm导出函数,如果是在rust环境里测试,会报错
cannot call wasm-bindgen imported functions on non-wasm targets
最佳实践就是分层设计,单独写wasm导出函数:
- 核心逻辑与 JS 绑定分离
- 普通 Rust 测试不依赖 wasm
- wasm 函数只是一个“外层包装器”
wasm-bindgen
如果你看过上一篇文章《# WebAssembly入门(一)——Emscripten》可能了解胶水代码的概念,wasm-bindgen就是提供js和rust交互的胶水代码用的。
其核心功能如下:
- 类型转换:
wasm-bindgen自动处理 Rust 和 JavaScript 之间的类型转换,例如将 Rust 的字符串、数组、结构体等映射为 JavaScript 中的对应类型。 - 导出与导入函数:通过使用
#[wasm_bindgen]属性宏,开发者可以轻松地将 Rust 函数导出给 JavaScript 调用,同时也可以在 Rust 中导入 JavaScript 函数。 - 内存管理:对于复杂类型如字符串或 DOM 对象,
wasm-bindgen会自动管理其生命周期,避免内存泄漏。 - 异步支持:它支持异步 Rust 代码,并能与 JavaScript 的异步机制协同工作。
- 事件与回调:支持定义和使用 JavaScript 的事件处理器和回调函数。
init函数
在使用 wasm-pack 打包 Rust 编写的 WebAssembly 模块时,生成的 JavaScript 胶水代码中会包含一个名为 init 的异步函数。这个函数是 WebAssembly 模块加载和初始化的核心入口,它负责完成以下关键任务:
- 异步加载
.wasm文件:通过fetch或import动态加载编译后的 WebAssembly 二进制文件。 - 实例化 Wasm 模块:将加载的字节码传递给浏览器的 WebAssembly API,创建可执行的模块实例。
- 绑定 JavaScript 接口:连接由
#[wasm_bindgen]标记的 Rust 函数与 JavaScript 环境,使它们能被直接调用。 - 初始化内存与全局状态:设置 Wasm 模块的线性内存、导出的全局变量和内部状态,确保模块运行时环境就绪。
init 函数返回一个 Promise,因此在使用时必须使用 await 或 .then() 等待其完成。只有在 init() 成功解析后,你才能安全地调用任何从 Rust 导出的函数。
init的promise返回值中也可以获取到导出的函数
init函数,初次执行完成后,可以重复执行,没有副作用,这一点很多ai都回答的不对,但如果初次加载时同时执行多次,就会创建多个wasm instance。