Rust WebAssembly开发实战
2025.08.13
Rust
7 min
2.8k 字
// 目录 · contents
前言 WebAssembly概述 工具链搭建 第一个Wasm项目 wasm-bindgen深入 JavaScript互操作 web-sys DOM操作 内存管理 性能优化 wasm-opt优化 大小优化策略 计算密集型任务性能 Component Model 与前端框架集成 适用场景 总结
前言
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 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup target add wasm32-unknown-unknown cargo install wasm-pack brew install binaryen
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-hellocd 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 [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 use wasm_bindgen::prelude::*;#[wasm_bindgen] pub fn greet (name: &str ) -> String { format! ("Hello, {}! From Rust Wasm" , name) }#[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
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 <!DOCTYPE html > <html > <body > <script type ="module" > import init, { greet, Calculator } from './pkg/wasm_hello.js' ; async function main ( ) { await init (); const message = greet ('World' ); document .body .textContent = message; const calc = new Calculator (); console .log (calc.add (1 , 2 )); console .log (calc.add (3 , 4 )); console .log (calc.get_history ()); } 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;#[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = setTimeout)] fn set_timeout (closure: &Closure<dyn FnMut ()>, millis: u32 ) -> i32 ; }#[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 () }#[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) }#[wasm_bindgen] pub fn set_callback (callback: &js_sys::Function) { let result = JsValue::from_str ("data from Rust" ); callback.call1 (&JsValue::NULL, &result).unwrap (); }#[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(); }
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(); 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 #[wasm_bindgen] pub fn process_string (input: &str ) -> String { input.to_uppercase () }#[wasm_bindgen] pub fn process_bytes (data: &[u8 ]) -> Vec <u8 > { data.iter ().map (|b| b.wrapping_add (1 )).collect () }#[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 ; pixels[i + 1 ] = 0 ; pixels[i + 2 ] = 0 ; pixels[i + 3 ] = 255 ; } pixels }
1 2 3 4 5 6 7 8 const memory = new WebAssembly .Memory ({ initial : 256 });const buffer = new Uint8Array (memory.buffer );const ptr = wasm_module.allocate (1024 );const view = new Uint8Array (memory.buffer , ptr, 1024 );
性能优化
wasm-opt优化
1 2 3 4 5 6 7 8 wasm-opt -Oz input.wasm -o output.wasm wasm-pack build --release
大小优化策略
1 2 3 4 5 6 7 [profile.release] opt-level = "z" lto = true codegen-units = 1 strip = true panic = "abort"
1 2 3 #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
1 2 3 4 5 6 7 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; } }
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 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 );console .time ('wasm' );grayscale (imageData.data );console .timeEnd ('wasm' ); 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' ); 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::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 import { defineConfig } from 'vite' ;import wasm from 'vite-plugin-wasm' ;export default defineConfig ({ plugins : [wasm ()], });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开发的核心要点:
wasm-pack 是核心工具链,集成了编译、绑定生成和优化
wasm-bindgen 提供Rust和JavaScript的无缝互操作
web-sys 提供Web API的Rust绑定,可以直接操作DOM
内存管理 需要理解Wasm线性内存和JS堆的交互
大小优化 :使用opt-level = "z"、LTO、wasm-opt等手段
适用于计算密集型任务 :图像处理、密码学、游戏引擎等
Component
Model 是未来方向,提供跨语言的标准化接口
Rust +
WebAssembly的组合为Web平台带来了接近原生的性能,是对JavaScript的有力补充,而非替代。