第9章 从 Rust 编译
Rust 是编译到 WebAssembly 的最佳语言之一,具有零成本抽象、内存安全、高性能等特点。本章将深入介绍如何使用 Rust 编译到 WebAssembly,包括工具链配置、最佳实践和性能优化。
9.1 Rust WebAssembly 工具链
9.1.1 环境搭建
安装 Rust 工具链:
# 安装 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# 安装 WebAssembly 目标
rustup target add wasm32-unknown-unknown
# 安装 wasm-pack(推荐)
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# 或者通过 cargo 安装
cargo install wasm-pack
# 安装其他有用工具
cargo install wasm-bindgen-cli
cargo install wasm-opt
验证安装:
# 检查 Rust 版本
rustc --version
cargo --version
# 检查 WebAssembly 目标
rustup target list | grep wasm32
# 检查 wasm-pack
wasm-pack --version
9.1.2 项目创建和配置
创建新项目:
# 使用 wasm-pack 模板
cargo generate --git https://github.com/rustwasm/wasm-pack-template
# 或手动创建
cargo new --lib my-wasm-project
cd my-wasm-project
Cargo.toml 配置:
[package]
name = "my-wasm-project"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # 生成动态库
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3" # JavaScript API 绑定
web-sys = "0.3" # Web API 绑定
# 可选的实用库
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.4"
console_error_panic_hook = "0.1"
[dependencies.web-sys]
version = "0.3"
features = [
"console",
"Document",
"Element",
"HtmlElement",
"Window",
]
# 优化配置
[profile.release]
opt-level = "s" # 优化体积
lto = true # 链接时优化
debug = false # 移除调试信息
panic = "abort" # panic 时直接终止
# 开发配置
[profile.dev]
opt-level = 0
debug = true
9.1.3 基本项目结构
my-wasm-project/
├── Cargo.toml
├── src/
│ ├── lib.rs # 主库文件
│ ├── utils.rs # 工具函数
│ └── math.rs # 数学计算模块
├── tests/
│ └── web.rs # 集成测试
├── pkg/ # 生成的 wasm 包(自动生成)
└── www/ # 前端示例(可选)
├── index.html
├── index.js
└── package.json
9.2 基础示例
9.2.1 Hello World
src/lib.rs:
use wasm_bindgen::prelude::*; // 导入 JavaScript 的 alert 函数 #[wasm_bindgen] extern "C" { fn alert(s: &str); } // 定义一个宏来简化 console.log #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } macro_rules! console_log { ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } // 导出到 JavaScript 的函数 #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } #[wasm_bindgen] pub fn say_hello() { console_log!("Hello from Rust and WebAssembly!"); } // 启动函数,在模块加载时自动执行 #[wasm_bindgen(start)] pub fn main() { console_log!("Rust WebAssembly module loaded!"); // 设置 panic hook 以便在浏览器中调试 #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); }
编译和使用:
# 使用 wasm-pack 编译
wasm-pack build --target web
# 生成的文件在 pkg/ 目录下
ls pkg/
# my_wasm_project.js
# my_wasm_project_bg.wasm
# my_wasm_project.d.ts
# package.json
HTML 使用示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Rust WebAssembly Demo</title>
</head>
<body>
<script type="module">
import init, { greet, say_hello } from './pkg/my_wasm_project.js';
async function run() {
// 初始化 wasm 模块
await init();
// 调用 Rust 函数
say_hello();
greet('WebAssembly');
}
run();
</script>
</body>
</html>
9.2.2 数学计算库
src/math.rs:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct Calculator { value: f64, } #[wasm_bindgen] impl Calculator { #[wasm_bindgen(constructor)] pub fn new() -> Calculator { Calculator { value: 0.0 } } #[wasm_bindgen(getter)] pub fn value(&self) -> f64 { self.value } #[wasm_bindgen(setter)] pub fn set_value(&mut self, value: f64) { self.value = value; } pub fn add(&mut self, other: f64) -> &mut Self { self.value += other; self } pub fn subtract(&mut self, other: f64) -> &mut Self { self.value -= other; self } pub fn multiply(&mut self, other: f64) -> &mut Self { self.value *= other; self } pub fn divide(&mut self, other: f64) -> Result<&mut Self, String> { if other == 0.0 { Err("Division by zero".to_string()) } else { self.value /= other; Ok(self) } } pub fn power(&mut self, exp: f64) -> &mut Self { self.value = self.value.powf(exp); self } pub fn sqrt(&mut self) -> Result<&mut Self, String> { if self.value < 0.0 { Err("Square root of negative number".to_string()) } else { self.value = self.value.sqrt(); Ok(self) } } pub fn reset(&mut self) -> &mut Self { self.value = 0.0; self } } // 静态函数 #[wasm_bindgen] pub fn factorial(n: u32) -> u64 { if n <= 1 { 1 } else { (2..=n as u64).product() } } #[wasm_bindgen] pub fn fibonacci(n: u32) -> u64 { match n { 0 => 0, 1 => 1, _ => { let mut a = 0u64; let mut b = 1u64; for _ in 2..=n { let temp = a + b; a = b; b = temp; } b } } } #[wasm_bindgen] pub fn gcd(mut a: u32, mut b: u32) -> u32 { while b != 0 { let temp = b; b = a % b; a = temp; } a } #[wasm_bindgen] pub fn lcm(a: u32, b: u32) -> u32 { if a == 0 || b == 0 { 0 } else { (a * b) / gcd(a, b) } } #[wasm_bindgen] pub fn prime_check(n: u32) -> bool { if n < 2 { return false; } if n == 2 { return true; } if n % 2 == 0 { return false; } let sqrt_n = (n as f64).sqrt() as u32; for i in (3..=sqrt_n).step_by(2) { if n % i == 0 { return false; } } true } }
lib.rs 中导出模块:
#![allow(unused)] fn main() { mod math; pub use math::*; mod utils; pub use utils::*; }
9.2.3 向量和矩阵运算
src/linear_algebra.rs:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; use std::fmt; #[wasm_bindgen] pub struct Vector3 { x: f64, y: f64, z: f64, } #[wasm_bindgen] impl Vector3 { #[wasm_bindgen(constructor)] pub fn new(x: f64, y: f64, z: f64) -> Vector3 { Vector3 { x, y, z } } #[wasm_bindgen(getter)] pub fn x(&self) -> f64 { self.x } #[wasm_bindgen(getter)] pub fn y(&self) -> f64 { self.y } #[wasm_bindgen(getter)] pub fn z(&self) -> f64 { self.z } #[wasm_bindgen(setter)] pub fn set_x(&mut self, x: f64) { self.x = x; } #[wasm_bindgen(setter)] pub fn set_y(&mut self, y: f64) { self.y = y; } #[wasm_bindgen(setter)] pub fn set_z(&mut self, z: f64) { self.z = z; } pub fn length(&self) -> f64 { (self.x * self.x + self.y * self.y + self.z * self.z).sqrt() } pub fn normalize(&mut self) -> &mut Self { let len = self.length(); if len > 0.0 { self.x /= len; self.y /= len; self.z /= len; } self } pub fn dot(&self, other: &Vector3) -> f64 { self.x * other.x + self.y * other.y + self.z * other.z } pub fn cross(&self, other: &Vector3) -> Vector3 { Vector3 { x: self.y * other.z - self.z * other.y, y: self.z * other.x - self.x * other.z, z: self.x * other.y - self.y * other.x, } } pub fn add(&self, other: &Vector3) -> Vector3 { Vector3 { x: self.x + other.x, y: self.y + other.y, z: self.z + other.z, } } pub fn subtract(&self, other: &Vector3) -> Vector3 { Vector3 { x: self.x - other.x, y: self.y - other.y, z: self.z - other.z, } } pub fn scale(&self, scalar: f64) -> Vector3 { Vector3 { x: self.x * scalar, y: self.y * scalar, z: self.z * scalar, } } pub fn distance_to(&self, other: &Vector3) -> f64 { let dx = self.x - other.x; let dy = self.y - other.y; let dz = self.z - other.z; (dx * dx + dy * dy + dz * dz).sqrt() } #[wasm_bindgen(js_name = toString)] pub fn to_string(&self) -> String { format!("Vector3({:.3}, {:.3}, {:.3})", self.x, self.y, self.z) } } #[wasm_bindgen] pub struct Matrix4x4 { elements: [f64; 16], // 列主序存储 } #[wasm_bindgen] impl Matrix4x4 { #[wasm_bindgen(constructor)] pub fn new() -> Matrix4x4 { Matrix4x4 { elements: [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ] } } pub fn identity() -> Matrix4x4 { Matrix4x4::new() } pub fn set(&mut self, row: usize, col: usize, value: f64) -> Result<(), String> { if row >= 4 || col >= 4 { return Err("Matrix index out of bounds".to_string()); } self.elements[col * 4 + row] = value; Ok(()) } pub fn get(&self, row: usize, col: usize) -> Result<f64, String> { if row >= 4 || col >= 4 { return Err("Matrix index out of bounds".to_string()); } Ok(self.elements[col * 4 + row]) } pub fn multiply(&self, other: &Matrix4x4) -> Matrix4x4 { let mut result = Matrix4x4::new(); for i in 0..4 { for j in 0..4 { let mut sum = 0.0; for k in 0..4 { sum += self.elements[k * 4 + i] * other.elements[j * 4 + k]; } result.elements[j * 4 + i] = sum; } } result } pub fn transform_vector(&self, vector: &Vector3) -> Vector3 { let x = self.elements[0] * vector.x + self.elements[4] * vector.y + self.elements[8] * vector.z + self.elements[12]; let y = self.elements[1] * vector.x + self.elements[5] * vector.y + self.elements[9] * vector.z + self.elements[13]; let z = self.elements[2] * vector.x + self.elements[6] * vector.y + self.elements[10] * vector.z + self.elements[14]; Vector3::new(x, y, z) } pub fn translate(x: f64, y: f64, z: f64) -> Matrix4x4 { let mut matrix = Matrix4x4::identity(); matrix.elements[12] = x; matrix.elements[13] = y; matrix.elements[14] = z; matrix } pub fn scale(x: f64, y: f64, z: f64) -> Matrix4x4 { let mut matrix = Matrix4x4::new(); matrix.elements[0] = x; matrix.elements[5] = y; matrix.elements[10] = z; matrix } pub fn rotate_x(angle_rad: f64) -> Matrix4x4 { let cos_a = angle_rad.cos(); let sin_a = angle_rad.sin(); let mut matrix = Matrix4x4::identity(); matrix.elements[5] = cos_a; matrix.elements[6] = sin_a; matrix.elements[9] = -sin_a; matrix.elements[10] = cos_a; matrix } pub fn rotate_y(angle_rad: f64) -> Matrix4x4 { let cos_a = angle_rad.cos(); let sin_a = angle_rad.sin(); let mut matrix = Matrix4x4::identity(); matrix.elements[0] = cos_a; matrix.elements[2] = -sin_a; matrix.elements[8] = sin_a; matrix.elements[10] = cos_a; matrix } pub fn rotate_z(angle_rad: f64) -> Matrix4x4 { let cos_a = angle_rad.cos(); let sin_a = angle_rad.sin(); let mut matrix = Matrix4x4::identity(); matrix.elements[0] = cos_a; matrix.elements[1] = sin_a; matrix.elements[4] = -sin_a; matrix.elements[5] = cos_a; matrix } pub fn determinant(&self) -> f64 { let e = &self.elements; // 4x4 矩阵行列式计算(使用第一行展开) e[0] * ( e[5] * (e[10] * e[15] - e[11] * e[14]) - e[6] * (e[9] * e[15] - e[11] * e[13]) + e[7] * (e[9] * e[14] - e[10] * e[13]) ) - e[4] * ( e[1] * (e[10] * e[15] - e[11] * e[14]) - e[2] * (e[9] * e[15] - e[11] * e[13]) + e[3] * (e[9] * e[14] - e[10] * e[13]) ) + e[8] * ( e[1] * (e[6] * e[15] - e[7] * e[14]) - e[2] * (e[5] * e[15] - e[7] * e[13]) + e[3] * (e[5] * e[14] - e[6] * e[13]) ) - e[12] * ( e[1] * (e[6] * e[11] - e[7] * e[10]) - e[2] * (e[5] * e[11] - e[7] * e[9]) + e[3] * (e[5] * e[10] - e[6] * e[9]) ) } pub fn get_elements(&self) -> Vec<f64> { self.elements.to_vec() } #[wasm_bindgen(js_name = toString)] pub fn to_string(&self) -> String { let mut result = String::from("Matrix4x4[\n"); for row in 0..4 { result.push_str(" ["); for col in 0..4 { result.push_str(&format!("{:8.3}", self.elements[col * 4 + row])); if col < 3 { result.push_str(", "); } } result.push_str("]\n"); } result.push(']'); result } } }
9.3 JavaScript 互操作
9.3.1 数据类型绑定
复杂数据结构绑定:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; use serde::{Deserialize, Serialize}; // 使用 serde 进行序列化 #[derive(Serialize, Deserialize)] #[wasm_bindgen] pub struct Person { name: String, age: u32, email: String, } #[wasm_bindgen] impl Person { #[wasm_bindgen(constructor)] pub fn new(name: String, age: u32, email: String) -> Person { Person { name, age, email } } #[wasm_bindgen(getter)] pub fn name(&self) -> String { self.name.clone() } #[wasm_bindgen(getter)] pub fn age(&self) -> u32 { self.age } #[wasm_bindgen(getter)] pub fn email(&self) -> String { self.email.clone() } #[wasm_bindgen(setter)] pub fn set_age(&mut self, age: u32) { self.age = age; } pub fn greet(&self) -> String { format!("Hello, I'm {} and I'm {} years old!", self.name, self.age) } } // 处理 JavaScript 对象 #[wasm_bindgen] pub fn process_person_data(value: &JsValue) -> Result<String, JsValue> { let person: Person = serde_wasm_bindgen::from_value(value.clone())?; Ok(format!("Processed: {}", person.greet())) } // 返回复杂数据给 JavaScript #[wasm_bindgen] pub fn create_person_list() -> Result<JsValue, JsValue> { let people = vec![ Person::new("Alice".to_string(), 30, "alice@example.com".to_string()), Person::new("Bob".to_string(), 25, "bob@example.com".to_string()), Person::new("Charlie".to_string(), 35, "charlie@example.com".to_string()), ]; serde_wasm_bindgen::to_value(&people).map_err(|err| err.into()) } // 处理数组 #[wasm_bindgen] pub fn sum_array(numbers: &[f64]) -> f64 { numbers.iter().sum() } #[wasm_bindgen] pub fn sort_array(numbers: &mut [f64]) { numbers.sort_by(|a, b| a.partial_cmp(b).unwrap()); } #[wasm_bindgen] pub fn filter_positive(numbers: Vec<f64>) -> Vec<f64> { numbers.into_iter().filter(|&x| x > 0.0).collect() } }
9.3.2 异步操作和 Promise
异步函数绑定:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; use web_sys::{Request, RequestInit, RequestMode, Response}; #[wasm_bindgen] pub async fn fetch_data(url: String) -> Result<JsValue, JsValue> { let mut opts = RequestInit::new(); opts.method("GET"); opts.mode(RequestMode::Cors); let request = Request::new_with_str_and_init(&url, &opts)?; let window = web_sys::window().unwrap(); let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; let resp: Response = resp_value.dyn_into().unwrap(); let text = JsFuture::from(resp.text()?).await?; Ok(text) } #[wasm_bindgen] pub async fn delay(ms: u32) -> Result<(), JsValue> { let promise = js_sys::Promise::new(&mut |resolve, _| { let closure = Closure::once_into_js(move || { resolve.call0(&JsValue::NULL).unwrap(); }); web_sys::window() .unwrap() .set_timeout_with_callback_and_timeout_and_arguments_0( closure.as_ref().unchecked_ref(), ms as i32, ) .unwrap(); }); JsFuture::from(promise).await?; Ok(()) } // 计算密集型异步任务 #[wasm_bindgen] pub async fn heavy_computation(n: u32) -> Result<u64, JsValue> { let mut result = 0u64; for i in 0..n { // 每1000次迭代让出控制权 if i % 1000 == 0 { delay(0).await?; } result = result.wrapping_add(fibonacci(i % 30) as u64); } Ok(result) } }
9.3.3 Web API 集成
DOM 操作:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; use web_sys::{console, Document, Element, HtmlElement, Window}; #[wasm_bindgen] pub struct DomHelper { document: Document, window: Window, } #[wasm_bindgen] impl DomHelper { #[wasm_bindgen(constructor)] pub fn new() -> Result<DomHelper, JsValue> { let window = web_sys::window().ok_or("No global window exists")?; let document = window.document().ok_or("Should have a document on window")?; Ok(DomHelper { document, window }) } pub fn create_element(&self, tag_name: &str) -> Result<Element, JsValue> { self.document.create_element(tag_name) } pub fn get_element_by_id(&self, id: &str) -> Option<Element> { self.document.get_element_by_id(id) } pub fn set_inner_html(&self, element_id: &str, html: &str) -> Result<(), JsValue> { if let Some(element) = self.get_element_by_id(element_id) { element.set_inner_html(html); Ok(()) } else { Err(JsValue::from_str(&format!("Element with id '{}' not found", element_id))) } } pub fn append_child(&self, parent_id: &str, child: &Element) -> Result<(), JsValue> { if let Some(parent) = self.get_element_by_id(parent_id) { parent.append_child(child)?; Ok(()) } else { Err(JsValue::from_str(&format!("Parent element with id '{}' not found", parent_id))) } } pub fn add_event_listener(&self, element_id: &str, event_type: &str, callback: &Closure<dyn FnMut()>) -> Result<(), JsValue> { if let Some(element) = self.get_element_by_id(element_id) { let html_element: HtmlElement = element.dyn_into()?; html_element.add_event_listener_with_callback(event_type, callback.as_ref().unchecked_ref())?; Ok(()) } else { Err(JsValue::from_str(&format!("Element with id '{}' not found", element_id))) } } pub fn log(&self, message: &str) { console::log_1(&JsValue::from_str(message)); } pub fn alert(&self, message: &str) { self.window.alert_with_message(message).unwrap(); } } // 实用的 DOM 操作函数 #[wasm_bindgen] pub fn create_button_with_callback( container_id: &str, button_text: &str, callback: &js_sys::Function ) -> Result<(), JsValue> { let dom = DomHelper::new()?; // 创建按钮 let button = dom.create_element("button")?; button.set_text_content(Some(button_text)); // 添加事件监听器 let closure = Closure::wrap(Box::new(move || { callback.call0(&JsValue::NULL).unwrap(); }) as Box<dyn FnMut()>); let html_button: HtmlElement = button.dyn_into()?; html_button.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())?; // 将按钮添加到容器 dom.append_child(container_id, &button)?; // 防止闭包被释放 closure.forget(); Ok(()) } }
9.4 性能优化
9.4.1 编译器优化
Cargo.toml 优化配置:
[profile.release]
# 体积优化
opt-level = "s" # 或 "z" 极致体积优化
lto = true # 链接时优化
codegen-units = 1 # 更好的优化,但编译慢
panic = "abort" # 减少 panic 处理代码
strip = true # 移除符号表
# 性能优化配置
[profile.performance]
inherits = "release"
opt-level = 3 # 最高性能优化
lto = "fat" # 全链接时优化
# 特定依赖的优化
[profile.dev.package."*"]
opt-level = 2 # 依赖库使用较高优化级别
wasm-pack 构建选项:
# 体积优化构建
wasm-pack build --target web --release -- --features "optimize-size"
# 性能优化构建
wasm-pack build --target web --release -- --features "optimize-speed"
# 使用 wasm-opt 进一步优化
wasm-pack build --target web --release
wasm-opt -Oz -o pkg/optimized.wasm pkg/my_project_bg.wasm
9.4.2 内存管理优化
自定义分配器:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; // 使用 wee_alloc 作为全局分配器(体积更小) #[cfg(feature = "wee_alloc")] #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; // 内存池示例 #[wasm_bindgen] pub struct MemoryPool<T> { pool: Vec<T>, available: Vec<usize>, capacity: usize, } #[wasm_bindgen] impl MemoryPool<f64> { #[wasm_bindgen(constructor)] pub fn new(capacity: usize) -> MemoryPool<f64> { let pool = Vec::with_capacity(capacity); let available = (0..capacity).collect(); MemoryPool { pool, available, capacity, } } pub fn allocate(&mut self, value: f64) -> Option<usize> { if let Some(index) = self.available.pop() { if index < self.pool.len() { self.pool[index] = value; } else { self.pool.push(value); } Some(index) } else { None } } pub fn deallocate(&mut self, index: usize) -> Result<(), String> { if index >= self.capacity { return Err("Index out of bounds".to_string()); } self.available.push(index); Ok(()) } pub fn get(&self, index: usize) -> Result<f64, String> { if index >= self.pool.len() { return Err("Index out of bounds".to_string()); } Ok(self.pool[index]) } pub fn set(&mut self, index: usize, value: f64) -> Result<(), String> { if index >= self.pool.len() { return Err("Index out of bounds".to_string()); } self.pool[index] = value; Ok(()) } } // 零拷贝字符串处理 #[wasm_bindgen] pub fn process_large_string(input: &str) -> String { // 避免不必要的字符串分配 input.lines() .filter(|line| !line.trim().is_empty()) .map(|line| line.trim()) .collect::<Vec<_>>() .join("\n") } // 使用 Vec<u8> 而不是 String 进行字节操作 #[wasm_bindgen] pub fn process_bytes(mut data: Vec<u8>) -> Vec<u8> { // 就地修改,避免额外分配 for byte in &mut data { *byte = byte.wrapping_add(1); } data } }
9.4.3 算法优化
SIMD 并行化:
#![allow(unused)] fn main() { use std::arch::wasm32::*; #[wasm_bindgen] pub fn simd_add_arrays(a: &[f32], b: &[f32]) -> Vec<f32> { let mut result = Vec::with_capacity(a.len()); let chunks = a.len() / 4; // SIMD 处理(每次处理4个元素) for i in 0..chunks { let offset = i * 4; unsafe { let va = v128_load(a.as_ptr().add(offset) as *const v128); let vb = v128_load(b.as_ptr().add(offset) as *const v128); let vr = f32x4_add(va, vb); let temp = [0f32; 4]; v128_store(temp.as_ptr() as *mut v128, vr); result.extend_from_slice(&temp); } } // 处理剩余元素 for i in (chunks * 4)..a.len() { result.push(a[i] + b[i]); } result } #[wasm_bindgen] pub fn simd_dot_product(a: &[f32], b: &[f32]) -> f32 { let mut sum = unsafe { f32x4_splat(0.0) }; let chunks = a.len() / 4; for i in 0..chunks { let offset = i * 4; unsafe { let va = v128_load(a.as_ptr().add(offset) as *const v128); let vb = v128_load(b.as_ptr().add(offset) as *const v128); let prod = f32x4_mul(va, vb); sum = f32x4_add(sum, prod); } } // 提取并累加 SIMD 结果 let mut result = unsafe { f32x4_extract_lane::<0>(sum) + f32x4_extract_lane::<1>(sum) + f32x4_extract_lane::<2>(sum) + f32x4_extract_lane::<3>(sum) }; // 处理剩余元素 for i in (chunks * 4)..a.len() { result += a[i] * b[i]; } result } }
缓存友好的算法:
#![allow(unused)] fn main() { #[wasm_bindgen] pub fn cache_friendly_matrix_multiply( a: &[f64], b: &[f64], c: &mut [f64], n: usize, block_size: usize ) { // 分块矩阵乘法,提高缓存命中率 for ii in (0..n).step_by(block_size) { for jj in (0..n).step_by(block_size) { for kk in (0..n).step_by(block_size) { let i_end = (ii + block_size).min(n); let j_end = (jj + block_size).min(n); let k_end = (kk + block_size).min(n); for i in ii..i_end { for j in jj..j_end { let mut sum = 0.0; for k in kk..k_end { sum += a[i * n + k] * b[k * n + j]; } c[i * n + j] += sum; } } } } } } // 预计算和查找表优化 #[wasm_bindgen] pub struct SinCosTable { sin_table: Vec<f32>, cos_table: Vec<f32>, table_size: usize, } #[wasm_bindgen] impl SinCosTable { #[wasm_bindgen(constructor)] pub fn new(table_size: usize) -> SinCosTable { let mut sin_table = Vec::with_capacity(table_size); let mut cos_table = Vec::with_capacity(table_size); for i in 0..table_size { let angle = 2.0 * std::f32::consts::PI * (i as f32) / (table_size as f32); sin_table.push(angle.sin()); cos_table.push(angle.cos()); } SinCosTable { sin_table, cos_table, table_size, } } pub fn sin(&self, angle: f32) -> f32 { let normalized = angle / (2.0 * std::f32::consts::PI); let index = ((normalized.fract() * self.table_size as f32) as usize) % self.table_size; self.sin_table[index] } pub fn cos(&self, angle: f32) -> f32 { let normalized = angle / (2.0 * std::f32::consts::PI); let index = ((normalized.fract() * self.table_size as f32) as usize) % self.table_size; self.cos_table[index] } } }
9.5 测试和调试
9.5.1 单元测试
tests/web.rs:
#![allow(unused)] fn main() { //! 浏览器环境下的测试 #![cfg(target_arch = "wasm32")] extern crate wasm_bindgen_test; use wasm_bindgen_test::*; use my_wasm_project::*; wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] fn test_calculator_basic_operations() { let mut calc = Calculator::new(); calc.add(10.0); assert_eq!(calc.value(), 10.0); calc.multiply(2.0); assert_eq!(calc.value(), 20.0); calc.subtract(5.0); assert_eq!(calc.value(), 15.0); calc.divide(3.0).unwrap(); assert!((calc.value() - 5.0).abs() < f64::EPSILON); } #[wasm_bindgen_test] fn test_division_by_zero() { let mut calc = Calculator::new(); calc.set_value(10.0); let result = calc.divide(0.0); assert!(result.is_err()); assert_eq!(calc.value(), 10.0); // 值不应该改变 } #[wasm_bindgen_test] fn test_vector_operations() { let v1 = Vector3::new(1.0, 2.0, 3.0); let v2 = Vector3::new(4.0, 5.0, 6.0); let dot = v1.dot(&v2); assert_eq!(dot, 32.0); // 1*4 + 2*5 + 3*6 = 32 let cross = v1.cross(&v2); assert_eq!(cross.x(), -3.0); assert_eq!(cross.y(), 6.0); assert_eq!(cross.z(), -3.0); let distance = v1.distance_to(&v2); assert!((distance - (27.0f64).sqrt()).abs() < f64::EPSILON); } #[wasm_bindgen_test] fn test_matrix_multiplication() { let m1 = Matrix4x4::identity(); let m2 = Matrix4x4::translate(1.0, 2.0, 3.0); let result = m1.multiply(&m2); // 单位矩阵乘以平移矩阵应该得到平移矩阵 assert_eq!(result.get(0, 3).unwrap(), 1.0); assert_eq!(result.get(1, 3).unwrap(), 2.0); assert_eq!(result.get(2, 3).unwrap(), 3.0); } #[wasm_bindgen_test] async fn test_async_computation() { let result = heavy_computation(1000).await.unwrap(); assert!(result > 0); } #[wasm_bindgen_test] fn test_prime_check() { assert!(!prime_check(1)); assert!(prime_check(2)); assert!(prime_check(3)); assert!(!prime_check(4)); assert!(prime_check(5)); assert!(!prime_check(9)); assert!(prime_check(17)); assert!(!prime_check(25)); } #[wasm_bindgen_test] fn test_fibonacci() { assert_eq!(fibonacci(0), 0); assert_eq!(fibonacci(1), 1); assert_eq!(fibonacci(2), 1); assert_eq!(fibonacci(3), 2); assert_eq!(fibonacci(4), 3); assert_eq!(fibonacci(5), 5); assert_eq!(fibonacci(10), 55); } }
运行测试:
# 安装测试运行器
cargo install wasm-pack
# 运行浏览器测试
wasm-pack test --headless --chrome
wasm-pack test --headless --firefox
# 运行 Node.js 测试
wasm-pack test --node
9.5.2 性能基准测试
benches/benchmark.rs:
#![allow(unused)] #![cfg(target_arch = "wasm32")] fn main() { extern crate wasm_bindgen_test; use wasm_bindgen_test::*; use web_sys::console; use my_wasm_project::*; wasm_bindgen_test_configure!(run_in_browser); fn measure_time<F, R>(name: &str, f: F) -> R where F: FnOnce() -> R, { let start = js_sys::Date::now(); let result = f(); let end = js_sys::Date::now(); console::log_1(&format!("{}: {:.2}ms", name, end - start).into()); result } #[wasm_bindgen_test] fn benchmark_fibonacci() { measure_time("Fibonacci(30)", || { fibonacci(30) }); measure_time("Fibonacci(35)", || { fibonacci(35) }); } #[wasm_bindgen_test] fn benchmark_vector_operations() { let v1 = Vector3::new(1.0, 2.0, 3.0); let v2 = Vector3::new(4.0, 5.0, 6.0); measure_time("Vector dot product (10000x)", || { for _ in 0..10000 { v1.dot(&v2); } }); measure_time("Vector cross product (10000x)", || { for _ in 0..10000 { v1.cross(&v2); } }); } #[wasm_bindgen_test] fn benchmark_matrix_operations() { let m1 = Matrix4x4::identity(); let m2 = Matrix4x4::translate(1.0, 2.0, 3.0); measure_time("Matrix multiplication (1000x)", || { for _ in 0..1000 { m1.multiply(&m2); } }); } #[wasm_bindgen_test] fn benchmark_simd_vs_scalar() { let a: Vec<f32> = (0..10000).map(|i| i as f32).collect(); let b: Vec<f32> = (0..10000).map(|i| (i * 2) as f32).collect(); measure_time("Scalar dot product", || { let mut sum = 0.0f32; for i in 0..a.len() { sum += a[i] * b[i]; } sum }); measure_time("SIMD dot product", || { simd_dot_product(&a, &b) }); } }
9.5.3 调试技巧
调试配置:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; // 调试宏 #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); #[wasm_bindgen(js_namespace = console)] fn error(s: &str); #[wasm_bindgen(js_namespace = console, js_name = log)] fn log_u32(a: u32); #[wasm_bindgen(js_namespace = console, js_name = log)] fn log_f64(a: f64); } macro_rules! console_log { ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } macro_rules! console_error { ($($t:tt)*) => (error(&format_args!($($t)*).to_string())) } // 条件编译的调试代码 #[wasm_bindgen] pub fn debug_function(value: f64) -> f64 { #[cfg(debug_assertions)] { console_log!("Debug: input value = {}", value); } let result = value * 2.0; #[cfg(debug_assertions)] { console_log!("Debug: result = {}", result); } result } // 错误处理和日志记录 #[wasm_bindgen] pub fn safe_divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { let error_msg = "Division by zero attempted"; console_error!("{}", error_msg); Err(error_msg.to_string()) } else { let result = a / b; console_log!("Division result: {} / {} = {}", a, b, result); Ok(result) } } // 性能监控 #[wasm_bindgen] pub struct PerformanceMonitor { start_time: f64, samples: Vec<f64>, } #[wasm_bindgen] impl PerformanceMonitor { #[wasm_bindgen(constructor)] pub fn new() -> PerformanceMonitor { PerformanceMonitor { start_time: js_sys::Date::now(), samples: Vec::new(), } } pub fn start_measurement(&mut self) { self.start_time = js_sys::Date::now(); } pub fn end_measurement(&mut self) -> f64 { let elapsed = js_sys::Date::now() - self.start_time; self.samples.push(elapsed); console_log!("Measurement: {:.2}ms", elapsed); elapsed } pub fn get_average(&self) -> f64 { if self.samples.is_empty() { 0.0 } else { self.samples.iter().sum::<f64>() / self.samples.len() as f64 } } pub fn get_stats(&self) -> String { if self.samples.is_empty() { return "No measurements".to_string(); } let avg = self.get_average(); let min = self.samples.iter().cloned().fold(f64::INFINITY, f64::min); let max = self.samples.iter().cloned().fold(f64::NEG_INFINITY, f64::max); format!("Samples: {}, Avg: {:.2}ms, Min: {:.2}ms, Max: {:.2}ms", self.samples.len(), avg, min, max) } } }
9.6 实际应用案例
9.6.1 游戏引擎核心
src/game_engine.rs:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; use std::collections::HashMap; #[wasm_bindgen] pub struct GameEngine { entities: HashMap<u32, Entity>, next_entity_id: u32, delta_time: f64, last_frame_time: f64, } #[wasm_bindgen] pub struct Entity { id: u32, position: Vector3, velocity: Vector3, rotation: Vector3, scale: Vector3, active: bool, } #[wasm_bindgen] impl Entity { #[wasm_bindgen(constructor)] pub fn new(id: u32) -> Entity { Entity { id, position: Vector3::new(0.0, 0.0, 0.0), velocity: Vector3::new(0.0, 0.0, 0.0), rotation: Vector3::new(0.0, 0.0, 0.0), scale: Vector3::new(1.0, 1.0, 1.0), active: true, } } #[wasm_bindgen(getter)] pub fn position(&self) -> Vector3 { Vector3::new(self.position.x(), self.position.y(), self.position.z()) } #[wasm_bindgen(setter)] pub fn set_position(&mut self, pos: Vector3) { self.position = pos; } pub fn translate(&mut self, delta: &Vector3) { self.position = self.position.add(delta); } pub fn update(&mut self, delta_time: f64) { if !self.active { return; } // 更新位置基于速度 let scaled_velocity = self.velocity.scale(delta_time); self.position = self.position.add(&scaled_velocity); } } #[wasm_bindgen] impl GameEngine { #[wasm_bindgen(constructor)] pub fn new() -> GameEngine { GameEngine { entities: HashMap::new(), next_entity_id: 1, delta_time: 0.0, last_frame_time: js_sys::Date::now(), } } pub fn create_entity(&mut self) -> u32 { let id = self.next_entity_id; self.next_entity_id += 1; let entity = Entity::new(id); self.entities.insert(id, entity); id } pub fn remove_entity(&mut self, id: u32) -> bool { self.entities.remove(&id).is_some() } pub fn get_entity(&self, id: u32) -> Option<Entity> { self.entities.get(&id).cloned() } pub fn update(&mut self) { let current_time = js_sys::Date::now(); self.delta_time = (current_time - self.last_frame_time) / 1000.0; self.last_frame_time = current_time; // 更新所有实体 for entity in self.entities.values_mut() { entity.update(self.delta_time); } } pub fn get_delta_time(&self) -> f64 { self.delta_time } pub fn get_entity_count(&self) -> u32 { self.entities.len() as u32 } } // 物理系统 #[wasm_bindgen] pub struct PhysicsWorld { gravity: Vector3, entities: Vec<u32>, engine: GameEngine, } #[wasm_bindgen] impl PhysicsWorld { #[wasm_bindgen(constructor)] pub fn new() -> PhysicsWorld { PhysicsWorld { gravity: Vector3::new(0.0, -9.81, 0.0), entities: Vec::new(), engine: GameEngine::new(), } } pub fn set_gravity(&mut self, gravity: Vector3) { self.gravity = gravity; } pub fn add_entity(&mut self, entity_id: u32) { self.entities.push(entity_id); } pub fn simulate_step(&mut self, delta_time: f64) { for &entity_id in &self.entities { if let Some(mut entity) = self.engine.get_entity(entity_id) { // 应用重力 let gravity_force = self.gravity.scale(delta_time); entity.velocity = entity.velocity.add(&gravity_force); // 更新实体 entity.update(delta_time); } } } } }
9.6.2 图像处理库
src/image_processing.rs:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct ImageProcessor { width: u32, height: u32, data: Vec<u8>, // RGBA 格式 } #[wasm_bindgen] impl ImageProcessor { #[wasm_bindgen(constructor)] pub fn new(width: u32, height: u32) -> ImageProcessor { let size = (width * height * 4) as usize; ImageProcessor { width, height, data: vec![0; size], } } pub fn load_from_array(&mut self, data: Vec<u8>) -> Result<(), String> { let expected_size = (self.width * self.height * 4) as usize; if data.len() != expected_size { return Err(format!("Data size mismatch: expected {}, got {}", expected_size, data.len())); } self.data = data; Ok(()) } pub fn get_data(&self) -> Vec<u8> { self.data.clone() } pub fn get_pixel(&self, x: u32, y: u32) -> Result<Vec<u8>, String> { if x >= self.width || y >= self.height { return Err("Pixel coordinates out of bounds".to_string()); } let index = ((y * self.width + x) * 4) as usize; Ok(vec![ self.data[index], // R self.data[index + 1], // G self.data[index + 2], // B self.data[index + 3], // A ]) } pub fn set_pixel(&mut self, x: u32, y: u32, r: u8, g: u8, b: u8, a: u8) -> Result<(), String> { if x >= self.width || y >= self.height { return Err("Pixel coordinates out of bounds".to_string()); } let index = ((y * self.width + x) * 4) as usize; self.data[index] = r; self.data[index + 1] = g; self.data[index + 2] = b; self.data[index + 3] = a; Ok(()) } pub fn apply_grayscale(&mut self) { for i in (0..self.data.len()).step_by(4) { let r = self.data[i] as f32; let g = self.data[i + 1] as f32; let b = self.data[i + 2] as f32; // 使用亮度公式 let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8; self.data[i] = gray; self.data[i + 1] = gray; self.data[i + 2] = gray; // Alpha 保持不变 } } pub fn adjust_brightness(&mut self, factor: f32) { for i in (0..self.data.len()).step_by(4) { self.data[i] = ((self.data[i] as f32 * factor).min(255.0).max(0.0)) as u8; self.data[i + 1] = ((self.data[i + 1] as f32 * factor).min(255.0).max(0.0)) as u8; self.data[i + 2] = ((self.data[i + 2] as f32 * factor).min(255.0).max(0.0)) as u8; // Alpha 保持不变 } } pub fn apply_blur(&mut self, radius: u32) { let mut new_data = self.data.clone(); let radius = radius as i32; for y in 0..self.height as i32 { for x in 0..self.width as i32 { let mut r_sum = 0u32; let mut g_sum = 0u32; let mut b_sum = 0u32; let mut count = 0u32; // 在半径范围内采样 for dy in -radius..=radius { for dx in -radius..=radius { let nx = x + dx; let ny = y + dy; if nx >= 0 && nx < self.width as i32 && ny >= 0 && ny < self.height as i32 { let index = ((ny * self.width as i32 + nx) * 4) as usize; r_sum += self.data[index] as u32; g_sum += self.data[index + 1] as u32; b_sum += self.data[index + 2] as u32; count += 1; } } } if count > 0 { let index = ((y * self.width as i32 + x) * 4) as usize; new_data[index] = (r_sum / count) as u8; new_data[index + 1] = (g_sum / count) as u8; new_data[index + 2] = (b_sum / count) as u8; // Alpha 保持不变 } } } self.data = new_data; } pub fn apply_edge_detection(&mut self) { let mut new_data = vec![0u8; self.data.len()]; // Sobel 算子 let sobel_x = [-1, 0, 1, -2, 0, 2, -1, 0, 1]; let sobel_y = [-1, -2, -1, 0, 0, 0, 1, 2, 1]; for y in 1..(self.height - 1) { for x in 1..(self.width - 1) { let mut gx = 0.0f32; let mut gy = 0.0f32; for i in 0..9 { let dx = (i % 3) as i32 - 1; let dy = (i / 3) as i32 - 1; let nx = x as i32 + dx; let ny = y as i32 + dy; let index = ((ny * self.width as i32 + nx) * 4) as usize; let gray = (self.data[index] as f32 * 0.299 + self.data[index + 1] as f32 * 0.587 + self.data[index + 2] as f32 * 0.114); gx += gray * sobel_x[i] as f32; gy += gray * sobel_y[i] as f32; } let magnitude = (gx * gx + gy * gy).sqrt().min(255.0) as u8; let index = ((y * self.width + x) * 4) as usize; new_data[index] = magnitude; new_data[index + 1] = magnitude; new_data[index + 2] = magnitude; new_data[index + 3] = self.data[index + 3]; // 保持 Alpha } } self.data = new_data; } #[wasm_bindgen(getter)] pub fn width(&self) -> u32 { self.width } #[wasm_bindgen(getter)] pub fn height(&self) -> u32 { self.height } } // 滤镜效果 #[wasm_bindgen] pub fn apply_sepia_filter(data: &mut [u8]) { for i in (0..data.len()).step_by(4) { let r = data[i] as f32; let g = data[i + 1] as f32; let b = data[i + 2] as f32; let tr = (0.393 * r + 0.769 * g + 0.189 * b).min(255.0); let tg = (0.349 * r + 0.686 * g + 0.168 * b).min(255.0); let tb = (0.272 * r + 0.534 * g + 0.131 * b).min(255.0); data[i] = tr as u8; data[i + 1] = tg as u8; data[i + 2] = tb as u8; } } #[wasm_bindgen] pub fn apply_invert_filter(data: &mut [u8]) { for i in (0..data.len()).step_by(4) { data[i] = 255 - data[i]; // R data[i + 1] = 255 - data[i + 1]; // G data[i + 2] = 255 - data[i + 2]; // B // Alpha 保持不变 } } }
本章小结
通过本章学习,你已经掌握了:
- Rust WebAssembly 工具链:环境搭建、项目配置和构建流程
- 基础编程:函数导出、数据类型绑定和错误处理
- JavaScript 互操作:复杂数据交换、异步操作和 Web API 集成
- 性能优化:编译器优化、内存管理和算法优化
- 测试调试:单元测试、性能基准和调试技巧
- 实际应用:游戏引擎和图像处理等复杂应用
🎯 重点技能:
- ✅ 熟练使用 Rust 编译到 WebAssembly
- ✅ 掌握 wasm-bindgen 和相关工具链
- ✅ 理解 Rust 的内存安全优势
- ✅ 能够构建高性能的 WebAssembly 应用
- ✅ 掌握与 JavaScript 的无缝集成
📚 下一步:第9章 练习题