WebAssembly(WASM)是一种低级二进制指令格式,为高性能 Web 应用提供了新的可能。Rust 凭借其内存安全特性和零成本抽象,成为编写 WASM 的理想选择。本教程将从零开始,带你逐步掌握 Rust 到 WASM 的开发流程。
准备工作
首先,我们需要安装必要的工具链:
安装 Rust:
访问rustup.rs,按照指示安装 Rust 工具链。安装完成后,你将拥有cargo
(Rust 的包管理器)和rustc
(编译器)。安装 wasm-pack:
这是一个将 Rust 代码编译为 WASM 并准备好在 Web 上使用的工具:
cargo install wasm-pack
第一个 Rust WASM 项目
让我们创建一个简单的项目,实现一个加法函数并在浏览器中调用它。
1. 创建新项目
wasm-pack new rust-wasm-demo
cd rust-wasm-demo
这个命令会创建一个基本的项目结构,其中包含:
Cargo.toml
:项目配置文件src/lib.rs
:Rust 源代码文件src/utils.rs
:一些辅助工具函数
2. 编写 Rust 代码
打开src/lib.rs
,替换为以下内容:
use wasm_bindgen::prelude::*;
// 暴露给JavaScript的函数
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 测试函数
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
#[wasm_bindgen]
属性是关键,它允许 Rust 函数被 JavaScript 调用,反之亦然。
3. 编译为 WASM
运行以下命令将 Rust 代码编译为 WebAssembly:
wasm-pack build --target web
这个命令会:
- 编译 Rust 代码为 WASM
- 生成 JavaScript 绑定
- 创建一个
pkg
目录,包含所有输出文件
编译成功后,pkg
目录中会有:
rust_wasm_demo_bg.wasm
:编译后的 WASM 二进制文件rust_wasm_demo.js
:自动生成的 JavaScript 绑定rust_wasm_demo.d.ts
:TypeScript 类型定义(可选)
4. 创建网页调用 WASM
在项目根目录创建一个wasmtest.html
文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Rust WASM Demo</title>
</head>
<body>
<h1>Rust WASM 加法演示</h1>
<input type="number" id="num1" value="2"> +
<input type="number" id="num2" value="3"> =
<span id="result"></span>
<button id="calculateBtn">计算</button>
<script type="module">
// 导入WASM模块
import init, { add } from './pkg/rust_wasm_demo.js';
// 初始化WASM
async function run() {
await init();
calculate(); // 初始计算
// 绑定按钮点击事件
document.getElementById('calculateBtn').addEventListener('click', calculate);
}
// 计算函数
function calculate() {
const num1 = parseInt(document.getElementById('num1').value);
const num2 = parseInt(document.getElementById('num2').value);
const result = add(num1, num2); // 调用Rust函数
document.getElementById('result').textContent = result;
}
// 启动应用
run();
</script>
</body>
</html>
5. 运行测试
由于浏览器的安全限制,我们不能直接通过file://
协议运行,需要一个本地服务器。你可以使用 任何简单的 HTTP 静态资源服务器,放置以上文件:
pkg/rust_wasm_demo.js
pkg/rust_wasm_demo_bg.wasm
wasmtest.html
通过访问http协议下的wasmtest.html文件,就可以看到和测试效果。
至此,我们完成了在JavaScript中如何调用WASM中的方法。那么接下来,我们反过来,让WASM调用Web API(也就是大家说的让WASM调用Web API)。既然WASM能调用Web API,那么也能调用你自定义的JavaScript函数。
与 DOM 交互
让我们升级示例,展示 Rust 如何与浏览器 DOM 交互。修改src/lib.rs
:
mod utils;
use wasm_bindgen::prelude::*;
use web_sys::{Document, Window};
// 获取浏览器窗口对象
#[wasm_bindgen]
pub fn get_window() -> Option<Window> {
web_sys::window()
}
// 获取文档对象
#[wasm_bindgen]
pub fn get_document() -> Option<Document> {
web_sys::window()?.document()
}
// 创建并添加元素到页面
#[wasm_bindgen]
pub fn add_hello_element() {
// 安全地获取文档对象
let document = match get_document() {
Some(doc) => doc,
None => return,
};
// 创建一个新的div元素
let div = match document.create_element("div") {
Ok(elem) => elem,
Err(_) => return,
};
// 设置元素内容
let _ = div.set_text_content(Some("Hello from Rust WASM!"));
// 添加样式
let _ = div.set_attribute("style", "color: blue; font-size: 20px; margin: 20px;");
// 添加到body
if let Some(body) = document.body() {
let _ = body.append_child(&div);
}
}
// 暴露给JavaScript的函数
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
//测试函数
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
更新Cargo.toml
,添加web-sys
依赖(在[dependencies]
部分):
web-sys = { version = "0.3", features = ["Document", "Window", "Element", "HtmlElement"] }
重新编译:
wasm-pack build --target web
更新wasmtest.html
,添加一个按钮来调用新函数:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Rust WASM Demo</title>
</head>
<body>
<h1>Rust WASM 加法演示</h1>
<input type="number" id="num1" value="2"> +
<input type="number" id="num2" value="3"> =
<span id="result"></span>
<button id="calculateBtn">计算</button>
<button id="btn_addHelloElement">添加Rust消息</button>
<script type="module">
// 导入WASM模块
import init, { add,add_hello_element } from './pkg/rust_wasm_demo.js';
// 初始化WASM
async function run() {
await init();
calculate(); // 初始计算
// 绑定按钮点击事件
document.getElementById('calculateBtn').addEventListener('click', calculate);
document.getElementById('btn_addHelloElement').addEventListener('click', addHelloElement);
}
// 计算函数
function calculate() {
const num1 = parseInt(document.getElementById('num1').value);
const num2 = parseInt(document.getElementById('num2').value);
const result = add(num1, num2); // 调用Rust函数
document.getElementById('result').textContent = result;
}
function addHelloElement() {
add_hello_element(); // 调用新的Rust函数
}
// 启动应用
run();
</script>
</body>
</html>
现在,我们重新进行测试,代开http协议下的wasmtest.html。点击【添加Rust消息】按钮三次,可以看到页面动态增加了三个div元素,其效果如下:
<div style="color: blue; font-size: 20px; margin: 20px;">Hello from Rust WASM!</div>
<div style="color: blue; font-size: 20px; margin: 20px;">Hello from Rust WASM!</div>
<div style="color: blue; font-size: 20px; margin: 20px;">Hello from Rust WASM!</div>
性能优势展示
WASM 的主要优势之一是性能。让我们创建一个计算密集型任务来展示这一点。在src/lib.rs
中添加:
斐波那契数列计算函数
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
升级后的lib.rs:
mod utils;
use wasm_bindgen::prelude::*;
use web_sys::{Document, Window};
// 斐波那契数列
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
// 获取浏览器窗口对象
#[wasm_bindgen]
pub fn get_window() -> Option<Window> {
web_sys::window()
}
// 获取文档对象
#[wasm_bindgen]
pub fn get_document() -> Option<Document> {
web_sys::window()?.document()
}
// 创建并添加元素到页面
#[wasm_bindgen]
pub fn add_hello_element() {
// 安全地获取文档对象
let document = match get_document() {
Some(doc) => doc,
None => return,
};
// 创建一个新的div元素
let div = match document.create_element("div") {
Ok(elem) => elem,
Err(_) => return,
};
// 设置元素内容
let _ = div.set_text_content(Some("Hello from Rust WASM!"));
// 添加样式
let _ = div.set_attribute("style", "color: blue; font-size: 20px; margin: 20px;");
// 添加到body
if let Some(body) = document.body() {
let _ = body.append_child(&div);
}
}
// 暴露给JavaScript的函数
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
//测试函数
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
在在wasmtest.html
中添加:
<h3>斐波那契计算</h3>
<input type="number" id="fibNum" value="30">
<button onclick="calculateFib()">计算斐波那契数</button>
<p id="fibResult"></p>
<script>
// 添加到现有脚本中
function calculateFib() {
const num = parseInt(document.getElementById('fibNum').value);
// 测试JavaScript性能
const jsStart = performance.now();
const jsResult = jsFibonacci(num);
const jsTime = performance.now() - jsStart;
// 测试WASM性能
const wasmStart = performance.now();
const wasmResult = fibonacci(num);
const wasmTime = performance.now() - wasmStart;
document.getElementById('fibResult').textContent =
`结果: ${wasmResult} | JS耗时: ${jsTime.toFixed(2)}ms | WASM耗时: ${wasmTime.toFixed(2)}ms`;
}
// JavaScript版本的斐波那契函数
function jsFibonacci(n) {
if (n === 0) return 0;
if (n === 1) return 1;
return jsFibonacci(n - 1) + jsFibonacci(n - 2);
}
</script>
升级后的wasmtest.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Rust WASM Demo</title>
</head>
<body>
<h1>Rust WASM 加法演示</h1>
<input type="number" id="num1" value="2"> +
<input type="number" id="num2" value="3"> =
<span id="result"></span>
<button id="calculateBtn">计算</button>
<button id="btn_addHelloElement">添加Rust消息</button>
</p>
<input type="number" id="fibNum" value="30">
<button id="btn_calculateFib">计算斐波那契数</button>
<p id="fibResult"></p>
<script type="module">
// 导入WASM模块
import init, { add,add_hello_element,fibonacci } from './pkg/rust_wasm_demo.js';
// 初始化WASM
async function run() {
await init();
calculate(); // 初始计算
// 绑定按钮点击事件
document.getElementById('calculateBtn').addEventListener('click', calculate);
document.getElementById('btn_addHelloElement').addEventListener('click', addHelloElement);
document.getElementById('btn_calculateFib').addEventListener('click', calculateFib);
}
// 计算函数
function calculate() {
const num1 = parseInt(document.getElementById('num1').value);
const num2 = parseInt(document.getElementById('num2').value);
const result = add(num1, num2); // 调用Rust函数
document.getElementById('result').textContent = result;
}
function addHelloElement() {
add_hello_element(); // 调用新的Rust函数
}
function calculateFib() {
const num = parseInt(document.getElementById('fibNum').value);
// 测试JavaScript性能
const jsStart = performance.now();
const jsResult = jsFibonacci(num);
const jsTime = performance.now() - jsStart;
// 测试WASM性能
const wasmStart = performance.now();
const wasmResult = fibonacci(num);
const wasmTime = performance.now() - wasmStart;
document.getElementById('fibResult').textContent =
`结果: ${wasmResult} | JS耗时: ${jsTime.toFixed(2)}ms | WASM耗时: ${wasmTime.toFixed(2)}ms`;
}
// JavaScript版本的斐波那契函数
function jsFibonacci(n) {
if (n === 0) return 0;
if (n === 1) return 1;
return jsFibonacci(n - 1) + jsFibonacci(n - 2);
}
// 启动应用
run();
</script>
</body>
</html>
重新编译并测试,浏览器显示的测试结果如下(以下是以30为参数的斐波那契函数计算结果,您的电脑性能可能超过我,所以展示出来的结果可能与我不同,但数据趋势应该是一致的,既:WASM的CPU密集运算型任务的执行速度远快于JavaScript)。
结果: 832040 | JS耗时: 32.70ms | WASM耗时: 3.00ms
笔者建议您下一步学习
恭喜你完成了 Rust WASM 的入门教程!以下是进一步学习的方向:
- 学习更多 wasm-bindgen、web-sys等库的更多特性:探索更复杂的类型转换和 JavaScript 交互
- 性能优化:学习如何使用
wasm-opt
等工具进一步优化 WASM 输出
如果有开发上的需要,可以:
- 学习 WebAssembly 的工作原理:了解 WASM 的二进制格式和执行模型
- 探索成熟框架:如 Yew 或 Seed,它们提供了类似 React 的组件模型