用 Rust 写 WebAssembly — 所有权模型遇上沙箱¶
1. 这一章讲什么¶
前面学会了「怎么让 JS 和 Wasm 对话」的通用规则,这一章把镜头对准写 Wasm 的那门语言。 书里的选择是 Rust,理由链条很清晰:Wasm 运行时的约束(要快、没有 GC、内存是平的、没人保护) 恰好一条条对上 Rust 的设计(零成本抽象、所有权、编译期检查)。 后半章是实操:导出函数、过边界传数据、调试。1
2. 顶层全景¶
Rust 为什么合身(四个理由)
│ 性能:零成本抽象 · 内存安全:所有权编译期查错 · 无 GC:对上 Wasm 极简运行时 · 工具链现成
▼
写代码:#[wasm_bindgen] 标出给 JS 的函数;类型只用 Wasm 认得的
│
▼
构建:wasm-pack build --target web → pkg/ 目录(.wasm + JS 胶水 + npm 包)
│
▼
过边界:标量直传 / 字符串 UTF-8 编解码 / Vec ↔ 数组 / 结构体走 serde
│
▼
调试:--dev 构建 + web_sys::console + DevTools
图说:上面是「为什么」,下面四步是「怎么做」。
3. 核心原理¶
3.1 四个理由,其实是一句话的四个侧面¶
书里列的四条,落点都是同一个:Rust 把别的语言交给运行时的活,全部挪到了编译期。2
性能——零成本抽象。 迭代器(逐个取元素的标准写法)、模式匹配这类高级特性编译后没有额外运行时开销; 底层经 LLVM 生成机器码,产物速度和原生可执行文件同档。浏览器要流畅动画、边缘服务器要低延迟, 吃的就是这一口。3
内存安全——所有权模型。 空指针、释放后重用、缓冲区溢出,是系统编程的常客。 Rust 的规矩:每个值只有一个主人;主人易手,旧变量立刻作废;借用(引用)有编译期检查。 这些错在编译期就直接报错,运行期零检查开销。 放到 Wasm 里尤其值钱:线性内存是一块没有操作系统保护的平地,编译期多挡一道,沙箱里就少一类事故。 书里点名的受益者场景:解析器、网络客户端(连接别人服务的那一方程序)这种吃不可信输入的程序。4
没有 GC。 Wasm 运行时故意极简,没有垃圾回收器,为的是执行可预测。
Rust 也恰好不靠 GC:分配显式(Box、Vec),释放跟着作用域走——变量离开作用域,内存自动还。
没有 GC 就没有不可预测的停顿,游戏、音频、IoT 边缘这些实时场景才轮得到它。5
工具链与生态。 wasm-pack(编译+绑定+打包一条命令)、cargo(依赖与构建)、 wasm-bindgen(类型安全的 JS 桥);crate(Rust 的包)生态里 serde(序列化)、 nalgebra(线性代数)、wgpu(图形)都能用。6
有名有姓的采用者(书里的名单):Figma 用 Rust/Wasm 做矢量渲染,3 倍性能; Cloudflare Workers 用它做 serverless;Dropbox 用它优化文件同步。7
3.2 给 Wasm 写 Rust 的三条纪律¶
- 导出靠注解:函数上标
#[wasm_bindgen],JS 才见得着; - 类型守规矩:参数返回只用 Wasm 认得的——
i32、f64、&str、Vec<T>;HashMap这类复杂类型没有直译,得先序列化; - 标准库有缺口:核心 Wasm 没有文件 I/O(
std::fs)和网络(std::net)—— 浏览器里走 JS 互操作拿能力,非浏览器环境走 WASI(第 10 章)。8
书里由浅入深的三个例子,正好把这三条各演一遍:
// 例 2:字符串(迭代器链,零成本)
#[wasm_bindgen]
pub fn reverse_string(input: &str) -> String {
input.chars().rev().collect()
}
// 例 3:结构体 + 方法(JS 里能 new、能调方法)
#[wasm_bindgen]
pub struct Point { x: f32, y: f32 }
#[wasm_bindgen]
impl Point {
#[wasm_bindgen(constructor)]
pub fn new(x: f32, y: f32) -> Point { Point { x, y } }
pub fn distance(&self, other: &Point) -> f32 {
((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
}
}
const p1 = new Point(0, 0);
const p2 = new Point(3, 4);
console.log(p1.distance(p2)); // 5 —— 3-4-5 直角三角形,能口算验证
#[wasm_bindgen(constructor)] 一个注解,new Point(3,4) 就在 JS 里可用了;
distance 在 JS 里就是 p1.distance(p2) 的方法调用。9
3.3 所有权、借用、生命周期:三分钟版¶
这一节是给「没写过 Rust 的人」的台阶,照书里的例子讲:10
所有权:每个值只有一个主人;主人易手(术语叫 move,移动),旧变量作废。
对照 C 的世界,这正是 double-free(同一块内存被释放两次)的结构性解药: Rust 里根本写不出「两个变量都以为自己该 free 它」的代码。11
借用:不交出所有权,只临时借引用。& 是只借读,&mut 是可写借;
函数签名里 fn print_length(s: &String) 就是「我只看看,不要你的」。12
生命周期:编译器给每个引用算「它指向的东西活得够不够久」,不够久就拒绝编译。
书里给的函数 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str 的意思是
「返回的那个引用,寿命不超过两个入参中较短的那个」——这类约束全是编译期算清的,
运行期不花一分钱。13
Wasm 语境下这三件套的价值一句话:安全性不依赖运行时检查, 而运行时检查在 Wasm 里要么没有、要么有成本。14
3.4 wasm-pack 工作流¶
五步,书里给的完整命令序列:15
cargo install wasm-pack # ① 装
cargo new --lib wasm_utils # ② 建库项目
# ③ Cargo.toml 加依赖:wasm-bindgen = "0.2"
# ④ src/lib.rs 写函数(上面那些)
wasm-pack build --target web # ⑤ 构建
第 ⑤ 步产出 pkg/ 目录:.wasm 本体 + .js 胶水 + package.json,
已经是一个可以 npm publish 的包。16
3.5 过边界传数据:每一类型的对应招式¶
| Rust 侧 | JS 侧 | 谁翻译 |
|---|---|---|
i32/f64 标量(单个的数) |
Number | 直接映射 |
&str / String |
String | UTF-8 编解码,垫片自动 |
Vec<i32>、&[i32] |
Array / Int32Array |
垫片序列化成线性内存切片 |
| 结构体 | 对象 | serde 序列化成 JSON(经 JsValue) |
数组的例子(书里原例,可复算):sum(values: &[i32]) 收一个整数切片,
JS 传 new Int32Array([10, 20, 30]),返回 60。17
结构体走 serde(书里的 User 例):#[derive(Serialize)] struct User { id: u32, name: String },
导出函数返回 JsValue::from_serde(&user)——JS 拿到的是普通对象 {"id":1,"name":"Alice"}。18
JsValue 是 wasm-bindgen 里「一个不透明的 JS 值」的类型:Rust 拿着它但不解它的内部,
适合传「过去就行」的对象。书里还提了两个进阶件:RefCell(Rust 内部可变性,绕过
「同一时刻只准一个可写借用」的默认约束)和 web_sys(整个浏览器 API 的 Rust 绑定)。
另外书里记了一笔时间坐标:Wasm GC 扩展在 2025 年中并入,之后 Rust 可以经 gc crate
的 Gc<T> 直接在 JS 堆里分配托管对象,树、图这类递归结构(自己套自己)不用序列化就能过界。19
3.6 调试与三个实用小例子¶
调试三板斧:wasm-pack build --dev(带 source map 的构建)、
web_sys::console 往控制台打日志(console::log_1(&format!("Logging value: {}", val).into()))、
DevTools 里断点 + 看 Wasm 栈帧 + 看映射回的 Rust 源码。20
实用例子三个:图像反色(*pixel = 255 - *pixel 逐字节,JS 传 Uint8Array)、
游戏位移(update_position = x + speed * delta,和 JS 渲染引擎搭档)、
以及用 Rust/Wasm + JS 前端攒一个「无后端微服务」。21
4. 作者的判断与证据¶
- 「Rust 所有权消除内存类 bug」:这是 Rust 官方与学界共识,可信;「编译期零开销」也是 设计事实。书里没吹牛;
- 采用者案例(Figma 3x、Cloudflare、Dropbox):方向属实(Figma 的工程博客确讲过 Wasm 渲染加速),「3x」是转述,未附出处链接;
- 「Wasm GC 2025 年中并入 MVP」:这是带时间戳的进展陈述,与公开时间线大体一致, 但「MVP」一词用得随意(MVP 指 2017 年首发版本,GC 是后续提案阶段并入)。
判断(我们的,不是书里的): 这章对 Rust 的推荐成立,但对新手隐去了一个真实的 学习成本:所有权与生命周期是 Rust 最广的劝退点,本章三个例子的体量还碰不到它的硬处 (跨函数返回引用、自引用结构),读者照抄能跑,一扩展就会撞上编译器。 如果错,会错在: 如果读者的目标只是「把一段计算快的逻辑搬进浏览器」, 那 AssemblyScript/TypeScript 路线的上手成本确实更低,而书里对这条对照路线几乎没有交代。
5. 边界与局限¶
- 没有对照:Rust 之外的 Wasm 源语言(AssemblyScript、C#、Go)本章零提及,选 Rust 是默认前提;
- 并发没展开:Rust 的所有权在多线程下的延伸(Send/Sync)没有讲,而第 5 章的多线程正好接这里;
- 错误处理一笔带过:Rust 的
Result/panic过 JS 边界会变成什么,没有完整走查; - 调试深度有限:源映射之外的进阶(比如 Chrome 的 DWARF 直读 Rust 变量)未提。
6. 可带走的¶
- Rust 配 Wasm 的四个理由,本质都是「编译期多干活,运行期少花钱」。
- 给 JS 用的函数标
#[wasm_bindgen];类型只用i32/f64/&str/Vec<T>。 std::fs/std::net在核心 Wasm 里不存在:浏览器找 JS 要,服务器找 WASI 要。- 所有权一句话:一个值一个主人,move 走旧作废——double-free 从根上写不出来。
- 借用
&/&mut管临时访问;生命周期是编译器给引用算的寿限。 wasm-pack build --target web出pkg/,已是 npm 包形态。Point例子的套路可复制:struct+impl+#[wasm_bindgen(constructor)]= JS 里的类。- 结构体过界用 serde +
JsValue;不透明的 JS 值也用JsValue装。 - 调试三件套:
--dev构建、web_sys::console、DevTools。 - Figma/Cloudflare/Dropbox 的先例说明这条路线是生产级,不是玩具。
7. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 四理由总览 | Rust for WebAssembly Development | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:65(搜「perfectly complement」) |
| 所有权与内存安全 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:83(搜「use-after-free errors」) |
| 无 GC 与确定性 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:97(搜「deterministic performance」) |
| 采用者案例 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:147(搜「Figma」) |
| 三条纪律 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:173(搜「Exporting Functions」) |
| 标准库缺口 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:211(搜「std::fs」) |
| Point 结构体例 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:305(搜「pub struct Point」) · text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:357(搜「new Point(0, 0)」) |
| 所有权/借用/生命周期 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:371(搜「single owner」) · text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:395(搜「Borrowing allows」) · text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:415(搜「how long references live」) |
| wasm-pack 五步 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:443(搜「cargo install wasm-pack」) |
| greet 与垫片职责 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:493(搜「pub fn greet」) · text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:511(搜「UTF-8」) |
| Vec/字符串/JsValue | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:523(搜「Vec<T> maps to JS Array」) · text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:529(搜「JsValue wrappers」) |
| Wasm GC 时间坐标 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:531(搜「Gc<T>」) |
| sum 数组例 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:551(搜「pub fn sum」) |
| serde User 例 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:573(搜「struct User」) |
| 调试三板斧 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:595(搜「build --dev」) |
| 反色/游戏位移例 | 同上 | text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:635(搜「255 - *pixel」) · text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:647(搜「update_position」) |
-
出处:「Rust for WebAssembly Development」第 13–27 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:13,搜「trifecta of speed, safety, and concurrency」)。 ↩ -
出处:「Rust for WebAssembly Development」第 65–73 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:69,搜「LLVM's backend」)。 ↩ -
出处:「Rust for WebAssembly Development」第 71–79 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:71,搜「zero-cost abstractions」)。 ↩ -
出处:「Rust for WebAssembly Development」第 81–93 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:85,搜「eliminates these at compile time」)。 ↩ -
出处:「Rust for WebAssembly Development」第 95–111 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:97,搜「intentionally minimal」)。 ↩ -
出处:「Rust for WebAssembly Development」第 113–141 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:117,搜「wasm-pack」)。 ↩ -
出处:「Rust for WebAssembly Development」第 143–163 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:147,搜「Figma」)与text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:151(搜「3x performance gains」)。 ↩ -
出处:「Rust for WebAssembly Development」第 171–219 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:177,搜「wasm_bindgen」)与text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:211(搜「std::fs」)。 ↩ -
出处:「Rust for WebAssembly Development」第 299–361 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:305,搜「pub struct Point」)与text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:361(搜「// 5」)。 ↩ -
出处:「Rust for WebAssembly Development」第 363–423 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:365,搜「ensures safety at compile time」)。 ↩ -
出处:「Rust for WebAssembly Development」第 383–391 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:385,搜「Prevents double-free errors」)。 ↩ -
出处:「Rust for WebAssembly Development」第 393–411 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:395,搜「temporary access」)。 ↩ -
出处:「Rust for WebAssembly Development」第 413–421 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:417,搜「fn longest」)。 ↩ -
出处:「Rust for WebAssembly Development」第 423 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:423,搜「reduce runtime checks」)。 ↩ -
出处:「Rust for WebAssembly Development」第 429–477 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:443,搜「cargo install wasm-pack」)。 ↩ -
出处:「Rust for WebAssembly Development」第 461–477 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:467,搜「pkg」)。 ↩ -
出处:「Rust for WebAssembly Development」第 547–561 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:551,搜「pub fn sum」)。 ↩ -
出处:「Rust for WebAssembly Development」第 563–587 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:585,搜「JsValue::from_serde」)。 ↩ -
出处:「Rust for WebAssembly Development」第 517–539 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:531,搜「Gc<T>」)与text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:533(搜「RefCell」)。 ↩ -
出处:「Rust for WebAssembly Development」第 589–623 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:595,搜「build --dev」)与text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:611(搜「Logging value」)。 ↩ -
出处:「Rust for WebAssembly Development」第 625–657 段(
text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:631,搜「pub fn invert」)与text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:647(搜「update_position」)。 ↩