Rust · #rust#wasm#webassembly

Rust WebAssembly开发实战

2025.08.13 Rust 7 min 2.8k
// 目录 · contents

前言

WebAssembly(Wasm)为Web带来了接近原生的执行性能。Rust凭借其无GC、零成本抽象和优秀的工具链,成为编写WebAssembly的首选语言。本文将从工具链搭建到实战项目,全面介绍Rust WebAssembly开发。

WebAssembly概述

graph TB
    subgraph "传统Web"
        JS[JavaScript] --> V8[V8/SpiderMonkey]
        V8 --> |JIT编译| MACHINE[Machine Code]
    end

    subgraph "WebAssembly"
        RUST[Rust/C/C++] --> |编译| WASM[.wasm Binary]
        WASM --> |加载| RUNTIME[Wasm Runtime]
        RUNTIME --> |接近原生速度| EXEC[Execution]
    end

    JS_BRIDGE[JavaScript] <-->|互操作| WASM

    style WASM fill:#6a1b9a,color:#fff

WebAssembly的特点: - 高性能:编译为紧凑的二进制格式,执行速度接近原生 - 安全:运行在沙箱中,内存隔离 - 跨平台:所有主流浏览器支持 - 语言无关:C、C++、Rust、Go等都可以编译到Wasm

工具链搭建

1
2
3
4
5
6
7
8
9
10
11
12
# 安装Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 添加wasm32目标
rustup target add wasm32-unknown-unknown

# 安装wasm-pack(Rust Wasm工具链核心)
cargo install wasm-pack

# 安装wasm-opt(优化工具,可选)
# 包含在binaryen中
brew install binaryen # macOS
flowchart LR
    RUST_SRC[Rust Source] --> |rustc| WASM_RAW[.wasm]
    WASM_RAW --> |wasm-bindgen| WASM_JS[.wasm + .js glue]
    WASM_JS --> |wasm-opt| WASM_OPT[Optimized .wasm]
    WASM_OPT --> |webpack/vite| BUNDLE[Web App]

    style RUST_SRC fill:#dcedc8
    style BUNDLE fill:#bbdefb

第一个Wasm项目

1
2
3
# 创建项目
cargo new --lib wasm-hello
cd wasm-hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Cargo.toml
[package]
name = "wasm-hello"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"

[dependencies.web-sys]
version = "0.3"
features = [
"console",
"Document",
"Element",
"HtmlElement",
"Node",
"Window",
]

[profile.release]
opt-level = "z" # 优化大小
lto = true # 链接时优化
codegen-units = 1 # 单代码生成单元
strip = true # 去除调试信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// src/lib.rs
use wasm_bindgen::prelude::*;

// 导出到JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}! From Rust Wasm", name)
}

// 调用JavaScript函数
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);

#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}

#[wasm_bindgen(start)]
pub fn main() {
log("Wasm module initialized!");
}

// 导出结构体
#[wasm_bindgen]
pub struct Calculator {
history: Vec<f64>,
}

#[wasm_bindgen]
impl Calculator {
#[wasm_bindgen(constructor)]
pub fn new() -> Calculator {
Calculator {
history: Vec::new(),
}
}

pub fn add(&mut self, a: f64, b: f64) -> f64 {
let result = a + b;
self.history.push(result);
result
}

pub fn get_history(&self) -> Vec<f64> {
self.history.clone()
}
}
1
2
3
4
5
6
7
8
# 构建
wasm-pack build --target web
# 生成 pkg/ 目录:
# ├── wasm_hello_bg.wasm (Wasm二进制)
# ├── wasm_hello_bg.wasm.d.ts (TypeScript类型)
# ├── wasm_hello.js (JS绑定)
# ├── wasm_hello.d.ts (TypeScript类型)
# └── package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- index.html -->
<!DOCTYPE html>
<html>
<body>
<script type="module">
import init, { greet, Calculator } from './pkg/wasm_hello.js';

async function main() {
await init();

// 调用Rust函数
const message = greet('World');
document.body.textContent = message;

// 使用Rust结构体
const calc = new Calculator();
console.log(calc.add(1, 2)); // 3
console.log(calc.add(3, 4)); // 7
console.log(calc.get_history()); // [3, 7]
}

main();
</script>
</body>
</html>

wasm-bindgen深入

JavaScript互操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
use wasm_bindgen::prelude::*;
use js_sys::{Array, Date, Function, Object, Promise, Reflect};
use wasm_bindgen_futures::JsFuture;

// 1. 调用JavaScript全局函数
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = setTimeout)]
fn set_timeout(closure: &Closure<dyn FnMut()>, millis: u32) -> i32;
}

// 2. 操作JavaScript对象
#[wasm_bindgen]
pub fn create_object() -> JsValue {
let obj = Object::new();
Reflect::set(&obj, &"name".into(), &"Rust".into()).unwrap();
Reflect::set(&obj, &"version".into(), &JsValue::from_f64(1.0)).unwrap();
obj.into()
}

// 3. 使用Promise(async/await)
#[wasm_bindgen]
pub async fn fetch_data(url: &str) -> Result<JsValue, JsValue> {
let window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_str(url)).await?;
let resp: web_sys::Response = resp_value.dyn_into()?;
let json = JsFuture::from(resp.json()?).await?;
Ok(json)
}

// 4. 回调函数
#[wasm_bindgen]
pub fn set_callback(callback: &js_sys::Function) {
let result = JsValue::from_str("data from Rust");
callback.call1(&JsValue::NULL, &result).unwrap();
}

// 5. 闭包(Closure)
#[wasm_bindgen]
pub fn create_timer() {
let closure = Closure::wrap(Box::new(|| {
web_sys::console::log_1(&"Timer fired!".into());
}) as Box<dyn FnMut()>);

web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
1000,
)
.unwrap();

closure.forget(); // 防止闭包被drop
}

web-sys DOM操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
use wasm_bindgen::prelude::*;
use web_sys::{Document, Element, HtmlElement};

#[wasm_bindgen]
pub fn setup_ui() -> Result<(), JsValue> {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();

// 创建元素
let container = document.create_element("div")?;
container.set_id("app");
container.set_class_name("container");

// 创建标题
let title = document.create_element("h1")?;
title.set_text_content(Some("Rust Wasm App"));

// 创建按钮
let button = document.create_element("button")?;
button.set_text_content(Some("Click me!"));

// 添加事件监听器
let click_count = std::cell::Cell::new(0u32);
let output = document.create_element("p")?;
output.set_id("output");

let output_clone = output.clone();
let closure = Closure::wrap(Box::new(move || {
let count = click_count.get() + 1;
click_count.set(count);
output_clone.set_text_content(
Some(&format!("Clicked {} times", count))
);
}) as Box<dyn FnMut()>);

button.add_event_listener_with_callback(
"click",
closure.as_ref().unchecked_ref(),
)?;
closure.forget();

// 组装DOM
container.append_child(&title)?;
container.append_child(&button)?;
container.append_child(&output)?;

document.body().unwrap().append_child(&container)?;

Ok(())
}

内存管理

graph TB
    subgraph "JavaScript"
        JS_MEM[JS Heap<br>GC管理]
        JS_REF[JsValue引用]
    end

    subgraph "WebAssembly"
        WASM_MEM[Linear Memory<br>连续字节数组]
        RUST_ALLOC[Rust Allocator<br>手动管理]
    end

    JS_REF <-->|wasm-bindgen| WASM_MEM
    RUST_ALLOC --> WASM_MEM

    style WASM_MEM fill:#6a1b9a,color:#fff
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 内存传递模式

// 1. 传递字符串(自动处理)
#[wasm_bindgen]
pub fn process_string(input: &str) -> String {
// wasm-bindgen自动在JS堆和Wasm内存之间复制字符串
input.to_uppercase()
}

// 2. 传递二进制数据
#[wasm_bindgen]
pub fn process_bytes(data: &[u8]) -> Vec<u8> {
// 自动处理内存复制
data.iter().map(|b| b.wrapping_add(1)).collect()
}

// 3. 零拷贝方式(高性能场景)
#[wasm_bindgen]
pub fn process_image(width: u32, height: u32) -> Vec<u8> {
let size = (width * height * 4) as usize;
let mut pixels = vec![0u8; size];

// 直接操作像素数据
for i in (0..size).step_by(4) {
pixels[i] = 255; // R
pixels[i + 1] = 0; // G
pixels[i + 2] = 0; // B
pixels[i + 3] = 255; // A
}

pixels
}
1
2
3
4
5
6
7
8
// JavaScript端的零拷贝访问
const memory = new WebAssembly.Memory({ initial: 256 });
const buffer = new Uint8Array(memory.buffer);

// 直接访问Wasm线性内存(适合大数据)
const ptr = wasm_module.allocate(1024);
const view = new Uint8Array(memory.buffer, ptr, 1024);
// 直接修改view,Rust侧可以看到变化

性能优化

wasm-opt优化

1
2
3
4
5
6
7
8
# 使用wasm-opt优化
wasm-opt -Oz input.wasm -o output.wasm
# -Oz: 优化大小(最激进)
# -O3: 优化速度
# -O2: 平衡优化

# wasm-pack自动调用wasm-opt
wasm-pack build --release

大小优化策略

1
2
3
4
5
6
7
# Cargo.toml - 优化Wasm大小
[profile.release]
opt-level = "z" # 优化大小
lto = true # 链接时优化
codegen-units = 1 # 单代码生成单元
strip = true # 去除符号
panic = "abort" # abort而非unwind,减小体积
1
2
3
// 使用wee_alloc替代默认分配器(减小约10KB)
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
1
2
3
4
5
6
7
# 分析Wasm大小
cargo install twiggy
twiggy top pkg/wasm_hello_bg.wasm
# 显示各函数占用的大小

twiggy dominators pkg/wasm_hello_bg.wasm
# 显示调用树和大小影响

计算密集型任务性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 图像处理示例 - 灰度转换
#[wasm_bindgen]
pub fn grayscale(pixels: &mut [u8]) {
for chunk in pixels.chunks_exact_mut(4) {
let r = chunk[0] as f32;
let g = chunk[1] as f32;
let b = chunk[2] as f32;
let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
chunk[0] = gray;
chunk[1] = gray;
chunk[2] = gray;
// chunk[3] (alpha) 保持不变
}
}

// 使用SIMD加速(需要simd128 feature)
// #[target_feature(enable = "simd128")]
// pub fn grayscale_simd(pixels: &mut [u8]) { ... }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// JavaScript端性能对比
import init, { grayscale } from './pkg/image_processor.js';

await init();

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

// Wasm处理
console.time('wasm');
grayscale(imageData.data);
console.timeEnd('wasm'); // ~2ms for 1920x1080

// JS处理
console.time('js');
for (let i = 0; i < imageData.data.length; i += 4) {
const gray = 0.299 * imageData.data[i] +
0.587 * imageData.data[i+1] +
0.114 * imageData.data[i+2];
imageData.data[i] = imageData.data[i+1] = imageData.data[i+2] = gray;
}
console.timeEnd('js'); // ~15ms for 1920x1080

ctx.putImageData(imageData, 0, 0);

Component Model

WebAssembly Component Model是下一代Wasm接口标准,支持更丰富的类型系统和组件间互操作。

graph TB
    subgraph "Component Model"
        WIT[WIT Interface<br>WebAssembly Interface Types] --> COMP1[Rust Component]
        WIT --> COMP2[Go Component]
        WIT --> COMP3[JS Component]

        COMP1 <-->|标准化接口| COMP2
        COMP2 <-->|标准化接口| COMP3
    end
1
2
3
4
5
6
7
8
9
10
// hello.wit - WIT接口定义
package example:hello;

interface greet {
greet: func(name: string) -> string;
}

world hello-world {
export greet;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 使用wit-bindgen实现Component
// Cargo.toml: wit-bindgen = "0.24"

wit_bindgen::generate!({
world: "hello-world",
});

struct MyComponent;

impl Guest for MyComponent {
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
}

export!(MyComponent);

与前端框架集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Vite + React集成
// vite.config.ts
import { defineConfig } from 'vite';
import wasm from 'vite-plugin-wasm';

export default defineConfig({
plugins: [wasm()],
});

// React组件中使用
import { useState, useEffect } from 'react';
import init, { Calculator } from '../pkg/wasm_hello';

function App() {
const [calc, setCalc] = useState<Calculator | null>(null);
const [result, setResult] = useState(0);

useEffect(() => {
init().then(() => {
setCalc(new Calculator());
});
}, []);

const handleAdd = () => {
if (calc) {
const r = calc.add(Math.random() * 100, Math.random() * 100);
setResult(r);
}
};

return (
<div>
<button onClick={handleAdd}>Add Random Numbers</button>
<p>Result: {result}</p>
</div>
);
}

适用场景

graph LR
    subgraph "适合Wasm的场景"
        A[图像/视频处理]
        B[密码学计算]
        C[游戏引擎]
        D[科学计算]
        E[编解码器]
        F[CAD/设计工具]
    end

    subgraph "不适合Wasm的场景"
        X[简单DOM操作]
        Y[表单验证]
        Z[普通API调用]
    end

    style A fill:#388e3c,color:#fff
    style X fill:#d32f2f,color:#fff

总结

Rust WebAssembly开发的核心要点:

  1. wasm-pack是核心工具链,集成了编译、绑定生成和优化
  2. wasm-bindgen提供Rust和JavaScript的无缝互操作
  3. web-sys提供Web API的Rust绑定,可以直接操作DOM
  4. 内存管理需要理解Wasm线性内存和JS堆的交互
  5. 大小优化:使用opt-level = "z"、LTO、wasm-opt等手段
  6. 适用于计算密集型任务:图像处理、密码学、游戏引擎等
  7. Component Model是未来方向,提供跨语言的标准化接口

Rust + WebAssembly的组合为Web平台带来了接近原生的性能,是对JavaScript的有力补充,而非替代。

作者 · authorzt
发布 · date2025-08-13
篇幅 · length2.8k 字 · 7 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论