WebAssembly入门(二)——Rust编译wasm全流程

127 阅读6分钟

本文将手把手介绍如何将rust编写的函数打包为wasm供js使用,并解释其关键配置和实践中的常见问题。

5分钟快速上手

1. 环境准备

rust和cargo

rust环境安装教程参考如下:

可执行rustc --versioncargo --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。

image.png


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

image.png 生成的文件里自带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

image.png


rust wasm进阶

本节将详细解释wasm打包中的关键角色。

编译目标 wasm32-unknown-unknown

如果你是第一次看到wasm32-unknown-unknown可能有点懵逼, wasm32-unknown-unknownRust 的编译目标(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-unknown32 位浏览器 / 纯 Web 环境❌ 无 std✅ 浏览器最常用目标,配合 wasm-bindgen
wasm64-unknown-unknown64 位浏览器(未来标准)❌ 无 std🧪 实验中,支持 64 位寻址(>4GB 内存)
wasm32-wasi32 位服务端 / CLI✅ 有 std✅ 支持文件、环境变量、socket
wasm64-wasi64 位服务端 / CLI✅ 有 std🧪 新兴,WASI 64-bit 支持大内存服务
wasm32-unknown-emscripten32 位浏览器 (旧方案)✅ 有 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 模块(通过 cdylibwasm32-* 目标)

crate-type 就是告诉编译器 “我要生成哪种类型的输出”。


主要类型说明:

类型说明适用场景
rlibRust 专用静态库格式(默认)✅ 供其他 Rust crate 依赖
cdylibC-compatible 动态库(推荐用于 FFI / WASM✅ 用于导出函数给其他语言(C, Python, JS等)
dylibRust 动态库(带 Rust 元数据)⚠️ 较少使用,只能被 Rust 加载
staticlib纯静态库(.a)✅ 方便嵌入 C/C++ 程序
bin可执行程序用于 CLI 应用

自定义类型的导出问题

如果导出的函数里有自定义类型,比如下面代码,就会提示

the trait bound CurveFitConfig: FromWasmAbi is 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返回值中也可以获取到导出的函数 image.png

init函数,初次执行完成后,可以重复执行,没有副作用,这一点很多ai都回答的不对,但如果初次加载时同时执行多次,就会创建多个wasm instance。 image.png