Rust WebAssembly 开发实战:构建高性能前端应用
介绍 Rust WebAssembly 开发实战。涵盖 Wasm 概念、优势及与 JS 互补关系。详细讲解环境搭建(wasm-pack)、Rust 与 JS 数据类型交互、异步操作。通过图片处理工具项目演示加载、灰度、模糊等图像处理功能。包含编译器优化、数据布局、内存管理及 SIMD 指令性能优化方案。最后介绍 Vite 打包及生产环境部署流程。适合希望提升 Web 端性能的开发者参考。

介绍 Rust WebAssembly 开发实战。涵盖 Wasm 概念、优势及与 JS 互补关系。详细讲解环境搭建(wasm-pack)、Rust 与 JS 数据类型交互、异步操作。通过图片处理工具项目演示加载、灰度、模糊等图像处理功能。包含编译器优化、数据布局、内存管理及 SIMD 指令性能优化方案。最后介绍 Vite 打包及生产环境部署流程。适合希望提升 Web 端性能的开发者参考。

💡 WebAssembly(Wasm)是一种二进制指令格式,旨在提供一种可移植的、高效的编译目标,允许开发者使用多种语言(如 C、C++、Rust)编写代码,并在 Web 浏览器中以接近原生速度运行。它填补了 JavaScript 在性能密集型任务上的空白,使得在 Web 端开发高性能应用成为可能。
Rust 语言以其内存安全、零成本抽象、高性能和良好的工具链支持,成为开发 WebAssembly 的首选语言之一。Rust 编译器可以直接将 Rust 代码编译成 WebAssembly,并且 Rust 的标准库提供了对 WebAssembly 的良好支持。此外,Rust 生态系统中还有许多专门为 WebAssembly 开发的库和工具,使得开发过程更加简单。
本章将深入探讨 Rust WebAssembly 开发的核心原理,介绍 WebAssembly 的概念、优势和应用场景,讲解如何使用 Rust 编译器将 Rust 代码编译成 WebAssembly,以及如何在 Web 浏览器中调用 WebAssembly 模块。同时,本章还将通过实战项目演示如何构建一个高性能的前端应用,帮助读者理解 Rust WebAssembly 开发的实际应用。
WebAssembly 是一种二进制指令格式,旨在提供一种可移植的、高效的编译目标,允许开发者使用多种语言编写代码,并在 Web 浏览器中以接近原生速度运行。它是 Web 平台的第四种语言,与 HTML、CSS 和 JavaScript 并列。
WebAssembly 的设计目标是:
WebAssembly 适用于以下场景:
WebAssembly 和 JavaScript 是互补的关系,而不是替代关系。WebAssembly 负责处理性能密集型任务,JavaScript 负责处理 DOM 操作和业务逻辑。
WebAssembly 的优势:
JavaScript 的优势:
wasm-pack 是 Rust 官方推荐的 WebAssembly 开发工具,它可以帮助开发者编译 Rust 代码成 WebAssembly 模块,并生成 JavaScript 绑定文件,方便在 Web 浏览器中调用。
安装 wasm-pack:
# 使用 cargo 安装
cargo install wasm-pack
# 验证安装
wasm-pack --version
cargo-generate 是一个 Cargo 子命令,用于从模板生成项目。wasm-pack 官方提供了一个 WebAssembly 项目模板,可以使用 cargo-generate 快速生成项目结构。
安装 cargo-generate:
# 使用 cargo 安装
cargo install cargo-generate
# 验证安装
cargo generate --version
使用 cargo-generate 从官方模板生成项目:
cargo generate --git https://github.com/rustwasm/wasm-pack-template --name hello-wasm
项目结构:
hello-wasm/
├── Cargo.toml
├── src/
│ ├── lib.rs
│ └── utils.rs
└── README.md
Cargo.toml:
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"
[dependencies]
wasm-bindgen = "0.2"
[lib]
crate-type = ["cdylib", "rlib"]
src/lib.rs:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
log(&format!("Hello, {}!", name));
}
src/utils.rs:
pub fn set_panic_hook() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
使用 wasm-pack 编译项目:
wasm-pack build --target web
编译过程会生成一个 pkg 目录,包含以下文件:
创建一个 index.html 文件,测试 WebAssembly 模块:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello, Wasm!</title>
</head>
<body>
<h1>Hello, Wasm!</h1>
<script type="module">
import init, { greet } from './pkg/hello_wasm.js';
async function run() {
await init();
greet('WebAssembly');
}
run();
</script>
</body>
</html>
使用 HTTP 服务器启动项目:
# 使用 Python 启动服务器
python3 -m http.server 8080
# 使用 Node.js 启动服务器
npx serve .
在浏览器中访问 http://localhost:8080,打开控制台,可以看到输出:"Hello, WebAssembly!"。
Rust 和 JavaScript 的数据类型不同,需要进行转换才能相互传递。wasm-bindgen 库提供了数据类型转换的支持。
基本数据类型(如 i32、u32、f32、f64、bool)可以直接传递:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[wasm_bindgen]
pub fn multiply(a: f64, b: f64) -> f64 {
a * b
}
#[wasm_bindgen]
pub fn is_even(n: i32) -> bool {
n % 2 == 0
}
在 JavaScript 中调用:
import init, { add, multiply, is_even } from './pkg/hello_wasm.js';
async function run() {
await init();
console.log(add(1, 2)); // 3
console.log(multiply(2.5, 3.2)); // 8
console.log(is_even(4)); // true
console.log(is_even(5)); // false
}
run();
Rust 的字符串类型是 String,JavaScript 的字符串类型是 String,需要使用 wasm_bindgen::JsValue 进行转换:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn to_upper_case(s: &str) -> String {
s.to_uppercase()
}
#[wasm_bindgen]
pub fn reverse(s: &str) -> String {
s.chars().rev().collect()
}
在 JavaScript 中调用:
import init, { to_upper_case, reverse } from './pkg/hello_wasm.js';
async function run() {
await init();
console.log(to_upper_case('hello')); // "HELLO"
console.log(reverse('hello')); // "olleh"
}
run();
Rust 的数组类型是 Vec,JavaScript 的数组类型是 Array,需要使用 wasm_bindgen::JsValue 进行转换:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn sum(v: Vec<i32>) -> i32 {
v.iter().sum()
}
#[wasm_bindgen]
pub fn filter_even(v: Vec<i32>) -> Vec<i32> {
v.into_iter().filter(|&n| n % 2 == 0).collect()
}
#[wasm_bindgen]
pub fn map_double(v: Vec<i32>) -> Vec<i32> {
v.into_iter().map(|n| n * 2).collect()
}
在 JavaScript 中调用:
import init, { sum, filter_even, map_double } from './pkg/hello_wasm.js';
async function run() {
await init();
console.log(sum([1, 2, 3, 4, 5])); // 15
console.log(filter_even([1, 2, 3, 4, 5])); // [2, 4]
console.log(map_double([1, 2, 3, 4, 5])); // [2, 4, 6, 8, 10]
}
run();
Rust 的结构体需要使用 wasm_bindgen 属性标记,才能在 JavaScript 中使用:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct Point {
pub x: f64,
pub y: f64,
}
#[wasm_bindgen]
impl Point {
#[wasm_bindgen(constructor)]
pub fn new(x: f64, y: f64) -> Point {
Point { x, y }
}
pub fn distance(&self, other: &Point) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
(dx * dx + dy * dy).sqrt()
}
pub fn move_by(&mut self, dx: f64, dy: f64) {
self.x += dx;
self.y += dy;
}
}
在 JavaScript 中调用:
import init, { Point } from './pkg/hello_wasm.js';
async function run() {
await init();
const p1 = new Point(1, 2);
const p2 = new Point(4, 6);
console.log(p1.distance(p2)); // 5
p1.move_by(2, 3);
console.log(p1.x); // 3
console.log(p1.y); // 5
}
run();
Rust 可以通过 wasm-bindgen 库调用 JavaScript 的函数和对象。
使用 extern "C" 块声明 JavaScript 函数:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = Math)]
fn random() -> f64;
#[wasm_bindgen(js_namespace = Math)]
fn floor(x: f64) -> f64;
}
#[wasm_bindgen]
pub fn random_int(min: i32, max: i32) -> i32 {
let range = max - min + 1;
let random = random();
let floor = floor(random * range as f64);
min + floor as i32
}
在 JavaScript 中调用:
import init, { random_int } from './pkg/hello_wasm.js';
async function run() {
await init();
console.log(random_int(1, 10)); // 随机整数 between 1 and 10
}
run();
使用 wasm_bindgen::JsValue 类型访问 JavaScript 对象:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn get_document_title() -> String {
let document = web_sys::window().unwrap().document().unwrap();
document.title()
}
#[wasm_bindgen]
pub fn set_document_title(title: &str) {
let document = web_sys::window().unwrap().document().unwrap();
document.set_title(title);
}
在 JavaScript 中调用:
import init, { get_document_title, set_document_title } from './pkg/hello_wasm.js';
async function run() {
await init();
console.log(get_document_title()); // 页面标题
set_document_title('New Title');
console.log(get_document_title()); // "New Title"
}
run();
Rust 的异步操作可以通过 wasm-bindgen-futures 库与 JavaScript 的 Promise 配合使用。
在 Cargo.toml 中添加依赖:
[dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "console"] }
使用 wasm_bindgen 属性标记异步函数:
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::window;
#[wasm_bindgen]
pub async fn fetch_data(url: &str) -> Result<String, JsValue> {
let window = window().unwrap();
let response = JsFuture::from(window.fetch_with_str(url)).await?;
let text = JsFuture::from(response.dyn_into::<web_sys::Response>()?.text()?).await?;
Ok(text.as_string().unwrap())
}
在 JavaScript 中调用:
import init, { fetch_data } from './pkg/hello_wasm.js';
async function run() {
await init();
try {
const data = await fetch_data('https://api.github.com/users/rustwasm');
console.log(data);
} catch (error) {
console.error(error);
}
}
run();
开发一个 WebAssembly 图片处理工具,支持以下功能:
image-processor/
├── Cargo.toml
├── src/
│ ├── lib.rs
│ └── utils.rs
├── static/
│ ├── index.html
│ └── style.css
└── README.md
[package]
name = "image-processor"
version = "0.1.0"
edition = "2021"
[dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "console", "CanvasRenderingContext2d", "HtmlCanvasElement", "HtmlImageElement"] }
image = "0.24"
use wasm_bindgen::prelude::*;
use web_sys::CanvasRenderingContext2d;
#[wasm_bindgen]
pub fn grayscale(data: &[u8], width: u32, height: u32) -> Vec<u8> {
let mut result = Vec::with_capacity(data.len());
for i in (0..data.len()).step_by(4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
let a = data[i + 3];
let gray = (0.299 * r as f32 + 0.587 * g as f32 + 0.114 * b as f32) as u8;
result.push(gray);
result.push(gray);
result.push(gray);
result.push(a);
}
result
}
#[wasm_bindgen]
pub (data: &[], width: , height: ) <> {
= ::(data.());
(..data.()).() {
= - data[i];
= - data[i + ];
= - data[i + ];
= data[i + ];
result.(r);
result.(g);
result.(b);
result.(a);
}
result
}
(data: &[], width: , height: ) <> {
= ::(data.());
= [ / , / , / , / , / , / , / , / , / ];
..height {
..width {
= ;
= ;
= ;
= ;
-..= {
-..= {
= (x + kx).(, width - ) ;
= (y + ky).(, height - ) ;
= (ny * width + nx) * ;
= kernel[(ky + ) * + (kx + )];
r += data[index] * weight;
g += data[index + ] * weight;
b += data[index + ] * weight;
a += data[index + ] * weight;
}
}
result.(r );
result.(g );
result.(b );
result.(a );
}
}
result
}
(data: &[], width: , height: ) <> {
= ::(data.());
= [, -, , -, , -, , -, ];
..height {
..width {
= ;
= ;
= ;
= ;
-..= {
-..= {
= (x + kx).(, width - ) ;
= (y + ky).(, height - ) ;
= (ny * width + nx) * ;
= kernel[(ky + ) * + (kx + )];
r += data[index] * weight;
g += data[index + ] * weight;
b += data[index + ] * weight;
a += data[index + ] * weight;
}
}
result.(r.(, ) );
result.(g.(, ) );
result.(b.(, ) );
result.(a.(, ) );
}
}
result
}
(url: &) <<>, JsValue> {
= image::(url).(|e| JsValue::(&e.()))?;
= img.();
(rgba.())
}
(data: &[], width: , height: , path: &) <(), JsValue> {
= image::RgbaImage::(width, height, data.()).(JsValue::())?;
img.(path).(|e| JsValue::(&e.()))?;
(())
}
pub fn set_panic_hook() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
body {
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
text-align: center;
}
header h1 {
margin-bottom: 10px;
font-size: 24px;
}
main {
display: grid;
grid-template-columns: 1fr 300px;
gap: 20px;
}
.content {
background-color: #fff;
padding: 20px;
: ;
: (, , , );
}
{
: block;
: ;
: solid ;
: ;
}
{
: ;
: ;
: ;
: (, , , );
}
{
: ;
: ;
}
{
: ;
}
{
: none;
}
{
: inline-block;
: ;
: ;
: ;
: ;
: pointer;
}
{
: ;
}
{
: flex;
: column;
: ;
: ;
}
{
: ;
: ;
: ;
: none;
: ;
: pointer;
}
{
: ;
}
{
: ;
}
{
: ;
: ;
: ;
: none;
: ;
: pointer;
}
{
: ;
}
{
: ;
}
{
: ;
: ;
: ;
: none;
: ;
: pointer;
}
{
: ;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebAssembly 图片处理工具</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header>
<h1>WebAssembly 图片处理工具</h1>
</header>
<main>
<div class="content">
<canvas id="canvas"></canvas>
</div>
<div class="controls">
图片操作
上传图片
灰度化
反转
模糊
锐化
保存图片
重置
wasm-pack build --target web
npx serve .
在浏览器中访问 http://localhost:3000/static/index.html,上传图片并测试图片处理功能。
Rust 编译器提供了多种优化级别,可以提高 WebAssembly 代码的性能。
在 Cargo.toml 中添加优化配置:
[profile.release]
lto = true
codegen-units = 1
opt-level = 'z' # 最小化代码大小
# opt-level = 's' # 平衡代码大小和性能
# opt-level = 3 # 最大化性能
WebAssembly 的内存模型是线性内存,数据布局对性能有很大影响。
使用 #[repr(C)] 属性对齐结构体:
#[repr(C)]
pub struct Point {
pub x: f64,
pub y: f64,
}
使用更小的数据类型,减少内存占用:
#[repr(C)]
pub struct Point {
pub x: i16,
pub y: i16,
}
WebAssembly 的内存是由 JavaScript 管理的,需要手动分配和释放内存。
使用栈分配代替堆分配,减少内存分配次数:
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
let c = a + b; // 栈分配
c
}
使用内存池管理频繁分配和释放的内存:
use std::collections::VecDeque;
use std::sync::Mutex;
use lazy_static::lazy_static;
lazy_static! {
static ref MEMORY_POOL: Mutex<VecDeque<Vec<u8>>> = Mutex::new(VecDeque::new());
}
#[wasm_bindgen]
pub fn allocate_buffer(size: usize) -> Vec<u8> {
let mut pool = MEMORY_POOL.lock().unwrap();
if let Some(buffer) = pool.pop_front() {
if buffer.capacity() >= size {
let mut buffer = buffer;
buffer.resize(size, 0);
return buffer;
}
}
Vec::with_capacity(size)
}
#[wasm_bindgen]
pub fn deallocate_buffer(buffer: Vec<u8>) {
let mut pool = MEMORY_POOL.lock().unwrap();
pool.push_back(buffer);
}
#[wasm_bindgen]
pub fn sum(v: Vec<i32>) -> i32 {
v.iter().sum() // 减少不必要的变量分配
}
WebAssembly 支持 SIMD(单指令多数据)指令,可以提高数据并行计算的性能。
在 Cargo.toml 中添加 SIMD 支持:
[dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "console", "CanvasRenderingContext2d", "HtmlCanvasElement", "HtmlImageElement"] }
image = "0.24"
wasm-simd = "0.1"
使用 SIMD 指令优化模糊算法:
use wasm_simd::*;
#[wasm_bindgen]
pub fn blur(data: &[u8], width: u32, height: u32) -> Vec<u8> {
let mut result = Vec::with_capacity(data.len());
let kernel = [1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0];
for y in 0..height {
for x in 0..width {
let mut r = 0.0;
let mut g = 0.0;
let mut b = 0.0;
let = ;
-..= {
-..= {
= (x + kx).(, width - ) ;
= (y + ky).(, height - ) ;
= (ny * width + nx) * ;
= kernel[(ky + ) * + (kx + )];
r += data[index] * weight;
g += data[index + ] * weight;
b += data[index + ] * weight;
a += data[index + ] * weight;
}
}
result.(r );
result.(g );
result.(b );
result.(a );
}
}
result
}
使用 webpack、rollup 或 vite 等打包工具打包 WebAssembly 应用。
安装 vite:
npm init -y
npm install -D vite
创建 vite.config.js:
import { defineConfig } from 'vite';
export default defineConfig({
build: {
outDir: 'dist',
assetsDir: 'assets',
rollupOptions: {
input: {
main: './static/index.html',
},
},
},
server: {
port: 3000,
open: true,
},
});
在 package.json 中添加脚本:
{
"name": "image-processor",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
将打包后的文件部署到生产环境:
Rust WebAssembly 开发是现代 Web 开发的重要技术,可以帮助开发者构建高性能的前端应用。Rust 语言的内存安全、零成本抽象、高性能和良好的工具链支持,使得它成为开发 WebAssembly 的首选语言之一。
本章介绍了 WebAssembly 的概念、优势和应用场景,讲解了如何搭建 Rust WebAssembly 开发环境,如何编译 Rust 代码成 WebAssembly,以及如何在 Web 浏览器中调用 WebAssembly 模块。同时,本章还通过实战项目演示了如何构建一个高性能的图片处理工具,帮助读者理解 Rust WebAssembly 开发的实际应用。
通过本章的学习,读者可以深入理解 Rust WebAssembly 开发的工作原理和实现方法,并在实际项目中应用这些技术。同时,本章也介绍了如何优化 Rust WebAssembly 代码的性能,帮助读者构建高性能的前端应用。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online