编程语言研究书库

用 Rust 写 WebAssembly — 所有权模型遇上沙箱

用 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 的三条纪律

  1. 导出靠注解:函数上标 #[wasm_bindgen],JS 才见得着;
  2. 类型守规矩:参数返回只用 Wasm 认得的——i32、f64、&str、Vec<T>; HashMap 这类复杂类型没有直译,得先序列化;
  3. 标准库有缺口:核心 Wasm 没有文件 I/O(std::fs)和网络(std::net)—— 浏览器里走 JS 互操作拿能力,非浏览器环境走 WASI(第 10 章)。8

书里由浅入深的三个例子,正好把这三条各演一遍:

// 例 1:标量
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 { a + b }
// 例 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,移动),旧变量作废。

let s = String::from("hello");
let t = s;            // s 移动给 t
// println!("{}", s); // 编译错:s 已失效

对照 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 / StringStringUTF-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. 可带走的

  1. Rust 配 Wasm 的四个理由,本质都是「编译期多干活,运行期少花钱」。
  2. 给 JS 用的函数标 #[wasm_bindgen];类型只用 i32/f64/&str/Vec<T>。
  3. std::fs/std::net 在核心 Wasm 里不存在:浏览器找 JS 要,服务器找 WASI 要。
  4. 所有权一句话:一个值一个主人,move 走旧作废——double-free 从根上写不出来。
  5. 借用 &/&mut 管临时访问;生命周期是编译器给引用算的寿限。
  6. wasm-pack build --target web 出 pkg/,已是 npm 包形态。
  7. Point 例子的套路可复制:struct + impl + #[wasm_bindgen(constructor)] = JS 里的类。
  8. 结构体过界用 serde + JsValue;不透明的 JS 值也用 JsValue 装。
  9. 调试三件套:--dev 构建、web_sys::console、DevTools。
  10. Figma/Cloudflare/Dropbox 的先例说明这条路线是生产级,不是玩具。

7. 原文地图

主题原书章原文位置
四理由总览Rust for WebAssembly Developmenttext/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」)

Footnotes

  1. 出处:「Rust for WebAssembly Development」第 13–27 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:13,搜「trifecta of speed, safety, and concurrency」)。 ↩

  2. 出处:「Rust for WebAssembly Development」第 65–73 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:69,搜「LLVM's backend」)。 ↩

  3. 出处:「Rust for WebAssembly Development」第 71–79 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:71,搜「zero-cost abstractions」)。 ↩

  4. 出处:「Rust for WebAssembly Development」第 81–93 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:85,搜「eliminates these at compile time」)。 ↩

  5. 出处:「Rust for WebAssembly Development」第 95–111 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:97,搜「intentionally minimal」)。 ↩

  6. 出处:「Rust for WebAssembly Development」第 113–141 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:117,搜「wasm-pack」)。 ↩

  7. 出处:「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」)。 ↩

  8. 出处:「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」)。 ↩

  9. 出处:「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」)。 ↩

  10. 出处:「Rust for WebAssembly Development」第 363–423 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:365,搜「ensures safety at compile time」)。 ↩

  11. 出处:「Rust for WebAssembly Development」第 383–391 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:385,搜「Prevents double-free errors」)。 ↩

  12. 出处:「Rust for WebAssembly Development」第 393–411 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:395,搜「temporary access」)。 ↩

  13. 出处:「Rust for WebAssembly Development」第 413–421 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:417,搜「fn longest」)。 ↩

  14. 出处:「Rust for WebAssembly Development」第 423 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:423,搜「reduce runtime checks」)。 ↩

  15. 出处:「Rust for WebAssembly Development」第 429–477 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:443,搜「cargo install wasm-pack」)。 ↩

  16. 出处:「Rust for WebAssembly Development」第 461–477 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:467,搜「pkg」)。 ↩

  17. 出处:「Rust for WebAssembly Development」第 547–561 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:551,搜「pub fn sum」)。 ↩

  18. 出处:「Rust for WebAssembly Development」第 563–587 段(text/17-fm-ultimate-webassembly-for-high-performance-apps-b.txt:585,搜「JsValue::from_serde」)。 ↩

  19. 出处:「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」)。 ↩

  20. 出处:「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」)。 ↩

  21. 出处:「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」)。 ↩

On this page