跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
RustNode.js大前端算法

Rust 与 WebAssembly 深度实战:在浏览器与 Node.js 中运行高性能代码

Rust 结合 WebAssembly 实现高性能计算,支持浏览器与 Node.js 环境。涵盖基础概念、编译工具链(wasm-pack)、双向交互(wasm-bindgen)、复杂数据类型处理及异步操作。通过图像滤镜与数据压缩案例展示实际应用场景,解决内存管理与类型转换问题,并提供优化部署方案。

忘忧发布于 2026/3/22更新于 2026/6/319 浏览
Rust 与 WebAssembly 深度实战:在浏览器与 Node.js 中运行高性能代码

Rust 与 WebAssembly 深度实战:在浏览器与 Node.js 中运行高性能代码

Rust WebAssembly 架构图

一、学习目标与重点

1.1 学习目标
  1. 理解 WebAssembly 基础:深入掌握 WebAssembly(Wasm/Wasmtime)的核心定义、运行机制、与 JavaScript 的性能对比。
  2. 掌握 Rust 到 Wasm 的编译:熟练使用 wasm-pack、cargo-web 等工具链,完成 Rust 代码到 Wasm 模块的编译、打包、优化。
  3. 精通 Rust 与 JavaScript 交互:实现双向交互(Rust 调用 JS 函数、JS 调用 Rust 函数),处理复杂数据类型(数组、对象、字符串),管理内存(Wasm 线性内存的分配与释放)。
  4. 开发真实 Wasm 应用:编写浏览器端高性能任务(Canvas 图像滤镜、WebGL 计算辅助)、Node.js 端计算密集型任务(图像处理、加密解密、数据压缩)。
  5. 优化 Wasm 模块:使用 wasm-opt 工具优化 Wasm 体积,学习代码分割、懒加载、模块缓存。
  6. 部署 Wasm 应用:部署到静态资源服务器、CDN,使用 Vite/Webpack 集成到 Vue/React 项目。
1.2 学习重点

💡 三大核心难点:

  1. Wasm 线性内存的管理:理解 Rust 的内存分配器(如 wasm-bindgen 的 __wbindgen_malloc/__wbindgen_free)如何与 Wasm 线性内存配合,避免内存泄漏。
  2. 复杂数据类型的转换:掌握 serde-wasm-bindgen、js-sys 等库如何将 Rust 的 Struct/Enum 与 JS 的 Object/Array/String 高效互转。
  3. 异步交互与 DOM 操作:使用 wasm-bindgen-futures、web-sys 等库实现异步任务(如 HTTP 请求)和 DOM 操作(如事件监听、元素创建)。

⚠️ 三大高频错误点:

  1. 未正确释放 Wasm 分配的内存:Rust 通过 wasm-bindgen 分配的内存(如 js_sys::ArrayBuffer、String::into_boxed_str)如果不手动或自动释放,会导致浏览器/Node.js 的内存泄漏。
  2. 数据类型转换时的边界检查:处理 Rust 的 u8/u16/i32 与 JS 的 Number 类型时,未进行边界检查会导致数据溢出。
  3. Wasm 模块加载失败:未正确配置 Webpack/Vite 的 Wasm loader,或未在浏览器中启用 Wasm 支持(现代浏览器默认已支持,但需检查浏览器版本)。

二、WebAssembly 基础

2.1 WebAssembly 的定义与特点

WebAssembly(Wasm)是一种可移植、高性能的低级字节码格式,设计用于在 Web 浏览器中运行,但现在也支持在 Node.js、Wasmtime、Wasmer 等独立运行时中运行。Wasm 的核心特点是:

  1. 高性能:Wasm 的执行速度接近原生代码(C/C++),比 JavaScript 快 10-100 倍。
  2. 可移植性:Wasm 模块可以在任何支持 Wasm 的运行时中运行,无需修改。
  3. 安全性:Wasm 运行在沙箱环境中,有严格的内存访问限制。
  4. 体积小:Wasm 模块的体积比 JavaScript 小,加载速度快。
2.2 Wasm 与 JavaScript 的性能对比
指标JavaScriptWebAssembly(Rust)
执行速度(计算密集型)🌟🌟🌟🌟🌟🌟
内存占用🌟🌟🌟🌟🌟🌟🌟
开发效率🌟🌟🌟🌟🌟🌟🌟🌟
调试难度🌟🌟🌟🌟🌟
生态系统🌟🌟🌟🌟🌟🌟🌟🌟
2.3 Wasm 的使用场景
  1. 浏览器端高性能任务:图像/视频处理、音频处理、WebGL/Canvas 计算、3D 渲染、加密解密。
  2. Node.js 端计算密集型任务:数据压缩、图像处理、机器学习推理、密码学计算。
  3. 物联网设备:使用 Wasmtime/Wasmer 在资源受限的设备上运行。
  4. 边缘计算:在 CDN 节点或边缘服务器上运行 Wasm 模块,减少网络延迟。

三、Rust 到 Wasm 的编译工具链

3.1 安装 wasm-pack

wasm-pack 是官方推荐的 Rust 到 Wasm 的编译工具链,它可以:

  1. 编译 Rust 代码到 Wasm 模块。
  2. 生成与 JavaScript 交互的绑定代码。
  3. 打包 Wasm 模块为 npm 包。
  4. 优化 Wasm 体积。

⌨️ 安装 wasm-pack:

# 使用 Cargo 安装
cargo install wasm-pack
3.2 安装 cargo-web

cargo-web 是另一个常用的 Rust 到 Wasm 的编译工具链,它支持:

  1. 热重载(Hot Reload)。
  2. 打包静态资源。
  3. 启动开发服务器。

⌨️ 安装 cargo-web:

# 使用 Cargo 安装(需要 Rust 1.70 或更高版本)
cargo install cargo-web --version 0.6.42

四、Rust 与 JavaScript 交互基础

4.1 创建一个简单的 Wasm 项目

使用 wasm-pack 创建一个简单的 Wasm 项目:

# 创建项目
wasm-pack new rust-wasm-demo
cd rust-wasm-demo
4.2 编写 Rust 代码

在 src/lib.rs 文件中编写 Rust 代码:

use wasm_bindgen::prelude::*;

// 使用#[wasm_bindgen]宏标记可以被JavaScript调用的函数
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}! This is Rust running in WebAssembly!", name)
}

// 计算斐波那契数列
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    if n == 0 || n == 1 {
        return n;
    }
    let mut a = 0;
    let mut b = 1;
    for _ in 2..=n {
        let c = a + b;
        a = b;
        b = c;
    }
    b
}

// 计算数组的平均值
#[wasm_bindgen]
pub fn average(arr: &[f64]) -> f64 {
    if arr.is_empty() {
        return 0.0;
    }
    let sum: f64 = arr.iter().sum();
    sum / arr.len() as f64
}
4.3 编译 Wasm 模块

使用 wasm-pack 编译 Wasm 模块:

# 编译为 npm 包
wasm-pack build --target web
4.4 编写 HTML 与 JavaScript 代码

在项目根目录下创建 index.html 文件:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust WebAssembly Demo</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
        .container { max-width: 800px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); }
        .result { margin-top: 20px; padding: 10px; background-color: #f0f0f0; border-radius: 4px; font-family: monospace; }
        button { margin-top: 10px; padding: 10px 20px; background-color: #4CAF50; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
        button:hover { background-color: #45a049; }
    </style>
</head>
<body>
<div class="container">
    <h1>Rust WebAssembly Demo</h1>
    <div><label for="name">姓名:</label><input type="text" id="name" value="张三"><button onclick="greet()">打招呼</button><div class="result" id="greet-result"></div></div>
    <div><label for="fibonacci-n">斐波那契数列第 n 项:</label><input type="number" id="fibonacci-n" value="40"><button onclick="calculateFibonacci()">计算</button><div class="result" id="fibonacci-result"></div></div>
    <div><label for="average-arr">数组(用逗号分隔):</label><input type="text" id="average-arr" value="1,2,3,4,5"><button onclick="calculateAverage()">计算平均值</button><div class="result" id="average-result"></div></div>
</div>
<script type="module">
    // 加载 Wasm 模块
    import init, { greet, fibonacci, average } from './pkg/rust_wasm_demo.js';
    async function run() {
        // 初始化 Wasm 模块
        await init();
        console.log('Rust WebAssembly 模块初始化成功');
    }
    run();

    // 打招呼
    function greet() {
        const name = document.getElementById('name').value;
        const result = greet(name);
        document.getElementById('greet-result').textContent = result;
    }

    // 计算斐波那契数列
    function calculateFibonacci() {
        const n = parseInt(document.getElementById('fibonacci-n').value);
        const start = performance.now();
        const result = fibonacci(n);
        const end = performance.now();
        const time = (end - start).toFixed(2);
        document.getElementById('fibonacci-result').textContent = `第${n}项:${result}, 耗时:${time}ms`;
    }

    // 计算数组的平均值
    function calculateAverage() {
        const arrStr = document.getElementById('average-arr').value;
        const arr = arrStr.split(',').map(parseFloat).filter(x => !isNaN(x));
        const result = average(arr);
        document.getElementById('average-result').textContent = `平均值:${result}`;
    }
</script>
</body>
</html>
4.5 运行 Wasm 应用

使用 Vite 启动开发服务器:

# 初始化 Vite 项目
npm init vite@latest . -- --template vanilla
# 安装依赖
npm install
# 运行开发服务器
npm run dev

五、Rust 与 JavaScript 交互进阶

5.1 处理复杂数据类型

使用 serde-wasm-bindgen 库处理复杂数据类型(如 Rust 的 Struct 与 JS 的 Object、Rust 的 Enum 与 JS 的 Object/Array/String)。

5.1.1 安装依赖

在 Cargo.toml 中添加依赖:

[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.5"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console", "Document", "Element", "HtmlElement"] }
5.1.2 编写 Rust 代码

在 src/lib.rs 文件中添加处理复杂数据类型的代码:

use wasm_bindgen::prelude::*;
use serde::Serialize;
use serde_wasm_bindgen::to_value;
use js_sys::Array;
use web_sys::console;

// 定义用户结构体
#[derive(Debug, Serialize)]
struct User {
    id: u32,
    username: String,
    email: String,
    age: Option<u8>,
    tags: Vec<String>,
}

// 定义产品枚举
#[derive(Debug, Serialize)]
enum Product {
    Book { title: String, author: String, price: f64 },
    Electronics { name: String, brand: String, price: f64, warranty: u32 },
    Clothing { name: String, size: String, price: f64 },
}

// 获取用户列表
#[wasm_bindgen]
pub fn get_users() -> JsValue {
    let users = vec![
        User { id: 1, username: "张三".to_string(), email: "[email protected]".to_string(), age: Some(25), tags: vec!["前端".to_string(), "Rust".to_string()], },
        User { id: 2, username: "李四".to_string(), email: "[email protected]".to_string(), age: None, tags: vec!["后端".to_string(), "Go".to_string()], },
        User { id: 3, username: "王五".to_string(), email: "[email protected]".to_string(), age: Some(30), tags: vec!["全栈".to_string(), "Python".to_string()], },
    ];
    to_value(&users).expect("序列化用户列表失败")
}

// 获取产品列表
#[wasm_bindgen]
pub fn get_products() -> JsValue {
    let products = vec![
        Product::Book { title: "Rust 语言开发从入门到精通".to_string(), author: "Rust 专家".to_string(), price: 99.0, },
        Product::Electronics { name: "MacBook Pro".to_string(), brand: "Apple".to_string(), price: 18999.0, warranty: 24, },
        Product::Clothing { name: "Rust T 恤".to_string(), size: "L".to_string(), price: 99.0, },
    ];
    to_value(&products).expect("序列化产品列表失败")
}

// 计算订单总价
#[wasm_bindgen]
pub fn calculate_total_price(cart: JsValue) -> f64 {
    let cart: Vec<(String, f64, u32)> = serde_wasm_bindgen::from_value(cart).expect("解析购物车失败");
    let total: f64 = cart.iter().map(|(name, price, quantity)| price * quantity as f64).sum();
    total
}
5.1.3 编写 JavaScript 代码

在 index.html 文件中添加处理复杂数据类型的代码:

// 获取用户列表
function getUsers() {
    const users = get_users();
    console.log('用户列表:', users);
    document.getElementById('users-result').textContent = JSON.stringify(users, null, 2);
}

// 获取产品列表
function getProducts() {
    const products = get_products();
    console.log('产品列表:', products);
    document.getElementById('products-result').textContent = JSON.stringify(products, null, 2);
}

// 计算订单总价
function calculateTotalPrice() {
    const cart = [
        { name: 'Rust 语言开发从入门到精通', price: 99.0, quantity: 1 },
        { name: 'Rust T 恤', price: 99.0, quantity: 2 },
        { name: 'MacBook Pro', price: 18999.0, quantity: 1 },
    ];
    const total = calculate_total_price(cart);
    document.getElementById('total-price-result').textContent = `订单总价:${total.toFixed(2)}元`;
}
5.2 异步交互

使用 wasm-bindgen-futures 库实现异步交互(如 HTTP 请求)。

5.2.1 安装依赖

在 Cargo.toml 中添加依赖:

[dependencies]
wasm-bindgen-futures = "0.4"
reqwest = { version = "0.11", features = ["json"] }
5.2.2 编写 Rust 代码

在 src/lib.rs 文件中添加异步交互的代码:

use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use reqwest::Client;
use serde::Deserialize;
use web_sys::console;

// 定义 GitHub 用户结构体
#[derive(Debug, Deserialize)]
struct GitHubUser {
    login: String,
    id: u32,
    avatar_url: String,
    html_url: String,
    name: Option<String>,
    bio: Option<String>,
    public_repos: u32,
    followers: u32,
    following: u32,
    created_at: String,
}

// 获取 GitHub 用户信息
#[wasm_bindgen]
pub fn get_github_user(username: &str) -> JsValue {
    let username = username.to_string();
    let promise = js_sys::Promise::new(&mut move |resolve, reject| {
        spawn_local(async move {
            let client = Client::new();
            let url = format!("https://api.github.com/users/{}", username);
            let result = client.get(&url).send().await.and_then(|response| response.json::<GitHubUser>().await);
            match result {
                Ok(user) => {
                    resolve.call1(&JsValue::NULL, &serde_wasm_bindgen::to_value(&user).expect("序列化 GitHub 用户失败")).unwrap();
                }
                Err(e) => {
                    reject.call1(&JsValue::NULL, &JsValue::from_str(&e.to_string())).unwrap();
                }
            }
        });
    });
    promise.into()
}
5.2.3 编写 JavaScript 代码

在 index.html 文件中添加异步交互的代码:

// 获取 GitHub 用户信息
async function getGitHubUser() {
    const username = document.getElementById('github-username').value;
    try {
        const user = await get_github_user(username);
        console.log('GitHub 用户信息:', user);
        document.getElementById('github-user-result').textContent = JSON.stringify(user, null, 2);
    } catch (e) {
        console.error('获取 GitHub 用户信息失败:', e);
        document.getElementById('github-user-result').textContent = `获取用户信息失败:${e}`;
    }
}
5.3 DOM 操作

使用 web-sys 库实现 DOM 操作(如事件监听、元素创建)。

5.3.1 安装依赖

在 Cargo.toml 中添加 web-sys 的 DOM 相关功能:

[dependencies.web-sys]
version = "0.3"
features = [
    "Document",
    "Element",
    "HtmlElement",
    "Node",
    "Window",
    "Event",
    "EventListener",
    "HtmlInputElement",
    "HtmlButtonElement",
    "HtmlDivElement",
    "HtmlHeadingElement",
    "HtmlParagraphElement",
]
5.3.2 编写 Rust 代码

在 src/lib.rs 文件中添加 DOM 操作的代码:

use wasm_bindgen::prelude::*;
use web_sys::{document, window, Event, HtmlInputElement, HtmlButtonElement, HtmlDivElement};

// 创建一个简单的待办事项列表
#[wasm_bindgen]
pub fn create_todo_list() {
    let document = document().expect("无法获取文档对象");
    // 创建容器
    let container = document.create_element("div").expect("无法创建容器");
    container.set_id("todo-container");
    container.set_inner_html(r#"
        <h2>待办事项列表</h2>
        <input type="text" placeholder="添加待办事项...">
        <button>添加</button>
        <div></div>
    "#);
    // 添加到文档中
    document.body().expect("无法获取 body 元素").append_child(&container).expect("无法添加容器");
    // 添加事件监听
    let add_button = document.get_element_by_id("todo-add-button").expect("无法找到添加按钮").dyn_into::<HtmlButtonElement>().expect("添加按钮类型不正确");
    let closure = Closure::wrap(Box::new(move |_e: Event| {
        let document = document().expect("无法获取文档对象");
        let input = document.get_element_by_id("todo-input").expect("无法找到输入框").dyn_into::<HtmlInputElement>().expect("输入框类型不正确");
        let text = input.value();
        if !text.is_empty() {
            add_todo_item(text.as_str());
            input.set_value("");
        }
    }) as Box<dyn FnMut(_)>);
    add_button.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()).expect("无法添加事件监听");
    closure.forget();
}

// 添加待办事项
fn add_todo_item(text: &str) {
    let document = document().expect("无法获取文档对象");
    let todo_list = document.get_element_by_id("todo-list").expect("无法找到待办事项列表").dyn_into::<HtmlDivElement>().expect("待办事项列表类型不正确");
    let todo_item = document.create_element("div").expect("无法创建待办事项");
    todo_item.set_id(&format!("todo-item-{}", chrono::Utc::now().timestamp_nanos()));
    todo_item.set_inner_html(&format!(r#"
        <span>{}</span>
        <button>删除</button>
    "#, text));
    todo_list.append_child(&todo_item).expect("无法添加待办事项");
    // 添加删除事件监听
    let remove_button = todo_item.get_elements_by_class_name("todo-remove-button").item(0).expect("无法找到删除按钮").dyn_into::<HtmlButtonElement>().expect("删除按钮类型不正确");
    let todo_item_clone = todo_item.clone();
    let closure = Closure::wrap(Box::new(move |_e: Event| {
        let document = document().expect("无法获取文档对象");
        let parent = document.get_element_by_id("todo-list").expect("无法找到待办事项列表").dyn_into::<HtmlDivElement>().expect("待办事项列表类型不正确");
        parent.remove_child(&todo_item_clone).expect("无法删除待办事项");
    }) as Box<dyn FnMut(_)>);
    remove_button.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref()).expect("无法添加事件监听");
    closure.forget();
}

六、真实案例应用

6.1 案例 1:浏览器端高性能 Canvas 图像滤镜

💡 场景分析:需要编写一个浏览器端的高性能图像滤镜,支持灰度、反转、模糊、锐化等滤镜效果,使用 Rust 编译成 Wasm 模块,提高滤镜的执行速度。

6.1.1 编写 Rust 代码

在 src/lib.rs 文件中添加图像滤镜的代码:

use wasm_bindgen::prelude::*;
use image::RgbaImage;
use image::Pixel;
use imageproc::contrast::adaptive_equalization;
use imageproc::filter::gaussian_blur_f32;
use imageproc::filter::sharpen;
use imageproc::ops::invert;
use web_sys::console;

// 将 base64 编码的图片数据转换为 RgbaImage
fn base64_to_image(base64: &str) -> RgbaImage {
    let base64 = base64.strip_prefix("data:image/png;base64,").unwrap_or(base64);
    let bytes = base64::decode(base64).expect("解析 base64 图片数据失败");
    let image = image::load_from_memory(&bytes).expect("加载图片失败").to_rgba8();
    image
}

// 将 RgbaImage 转换为 base64 编码的图片数据
fn image_to_base64(image: RgbaImage) -> String {
    let mut buffer = Vec::new();
    image::write_buffer_with_format(&mut buffer, &image, image::ImageOutputFormat::Png).expect("保存图片失败");
    format!("data:image/png;base64,{}", base64::encode(buffer))
}

// 灰度滤镜
#[wasm_bindgen]
pub fn apply_grayscale(base64: &str) -> String {
    let mut image = base64_to_image(base64);
    for pixel in image.pixels_mut() {
        let r = pixel[0] as u32;
        let g = pixel[1] as u32;
        let b = pixel[2] as u32;
        let gray = (r * 0.299 + g * 0.587 + b * 0.114) as u8;
        pixel[0] = gray;
        pixel[1] = gray;
        pixel[2] = gray;
    }
    image_to_base64(image)
}

// 反转滤镜
#[wasm_bindgen]
pub fn apply_invert(base64: &str) -> String {
    let mut image = base64_to_image(base64);
    invert(&mut image);
    image_to_base64(image)
}

// 模糊滤镜
#[wasm_bindgen]
pub fn apply_blur(base64: &str, sigma: f32) -> String {
    let image = base64_to_image(base64);
    let blurred = gaussian_blur_f32(&image, sigma);
    image_to_base64(blurred)
}

// 锐化滤镜
#[wasm_bindgen]
pub fn apply_sharpen(base64: &str, amount: f32) -> String {
    let image = base64_to_image(base64);
    let sharpened = sharpen(&image, amount);
    image_to_base64(sharpened)
}

// 自适应直方图均衡化滤镜
#[wasm_bindgen]
pub fn apply_contrast(base64: &str) -> String {
    let image = base64_to_image(base64);
    let equalized = adaptive_equalization(&image, 8, 8, 0.15);
    image_to_base64(equalized)
}
6.1.2 编写 HTML 与 JavaScript 代码

在 index.html 文件中添加图像滤镜的代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust Wasm Canvas 图像滤镜</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }
        .container { max-width: 1000px; margin: 0 auto; background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); }
        .image-container { display: flex; gap: 20px; margin-top: 20px; }
        .image-wrapper { flex: 1; }
        .image-wrapper h3 { text-align: center; margin-bottom: 10px; }
        .image-wrapper img { width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px; }
        .controls { margin-top: 20px; padding: 10px; background-color: #f0f0f0; border-radius: 4px; }
        .controls label { margin-right: 10px; }
        .controls input, .controls select, .controls button { margin-right: 10px; padding: 5px; border: 1px solid #ddd; border-radius: 4px; }
    </style>
</head>
<body>
<div class="container">
    <h1>Rust Wasm Canvas 图像滤镜</h1>
    <div class="controls">
        <input type="file" id="image-file" accept="image/png,image/jpeg,image/jpg">
        <select id="filter-type">
            <option value="grayscale">灰度</option>
            <option value="invert">反转</option>
            <option value="blur">模糊</option>
            <option value="sharpen">锐化</option>
            <option value="contrast">对比度增强</option>
        </select>
        <input type="number" id="blur-sigma" value="2.0" step="0.1" min="0.1" max="10.0" style="display:none;">
        <input type="number" id="sharpen-amount" value="1.5" step="0.1" min="0.1" max="10.0" style="display:none;">
        <button onclick="applyFilter()">应用滤镜</button>
    </div>
    <div class="image-container">
        <div class="image-wrapper"><h3>原图</h3><img id="original-image" src="" alt="原图"></div>
        <div class="image-wrapper"><h3>处理后</h3><img id="filtered-image" src="" alt="处理后"></div>
    </div>
</div>
<script type="module">
    import init, { apply_grayscale, apply_invert, apply_blur, apply_sharpen, apply_contrast } from './pkg/rust_wasm_demo.js';
    await init();

    // 加载图片
    document.getElementById('image-file').addEventListener('change', function(e) {
        const file = e.target.files[0];
        const reader = new FileReader();
        reader.onload = function(e) {
            document.getElementById('original-image').src = e.target.result;
            document.getElementById('filtered-image').src = '';
        };
        reader.readAsDataURL(file);
    });

    // 显示/隐藏滤镜参数输入框
    document.getElementById('filter-type').addEventListener('change', function(e) {
        const blurSigmaInput = document.getElementById('blur-sigma');
        const sharpenAmountInput = document.getElementById('sharpen-amount');
        if (e.target.value === 'blur') {
            blurSigmaInput.style.display = 'inline';
            sharpenAmountInput.style.display = 'none';
        } else if (e.target.value === 'sharpen') {
            blurSigmaInput.style.display = 'none';
            sharpenAmountInput.style.display = 'inline';
        } else {
            blurSigmaInput.style.display = 'none';
            sharpenAmountInput.style.display = 'none';
        }
    });

    // 应用滤镜
    async function applyFilter() {
        const originalImage = document.getElementById('original-image');
        if (!originalImage.src) {
            alert('请先选择图片');
            return;
        }
        // 读取图片数据
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        const img = new Image();
        img.onload = async function() {
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img, 0, 0);
            const dataURL = canvas.toDataURL('image/png');

            // 应用滤镜
            let filteredDataURL;
            const filterType = document.getElementById('filter-type').value;
            switch (filterType) {
                case 'grayscale': filteredDataURL = apply_grayscale(dataURL); break;
                case 'invert': filteredDataURL = apply_invert(dataURL); break;
                case 'blur': const sigma = parseFloat(document.getElementById('blur-sigma').value); filteredDataURL = apply_blur(dataURL, sigma); break;
                case 'sharpen': const amount = parseFloat(document.getElementById('sharpen-amount').value); filteredDataURL = apply_sharpen(dataURL, amount); break;
                case 'contrast': filteredDataURL = apply_contrast(dataURL); break;
                default: alert('无效的滤镜类型'); return;
            }
            // 显示处理后的图片
            document.getElementById('filtered-image').src = filteredDataURL;
        };
        img.src = originalImage.src;
    }
</script>
</body>
</html>
6.2 案例 2:Node.js 端计算密集型任务——数据压缩

💡 场景分析:需要编写一个 Node.js 端的计算密集型任务——数据压缩,使用 Rust 编译成 Wasm 模块,提高压缩的执行速度。

6.2.1 编写 Rust 代码

在 src/lib.rs 文件中添加数据压缩的代码:

use wasm_bindgen::prelude::*;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
use base64;

// 压缩字符串
#[wasm_bindgen]
pub fn compress_string(input: &str, level: u32) -> String {
    let mut encoder = GzEncoder::new(Vec::new(), Compression::new(level));
    encoder.write_all(input.as_bytes()).expect("压缩失败");
    let compressed = encoder.finish().expect("压缩失败");
    base64::encode(compressed)
}

// 解压缩字符串
#[wasm_bindgen]
pub fn decompress_string(input: &str) -> String {
    let compressed = base64::decode(input).expect("解析 base64 数据失败");
    let mut decoder = flate2::read::GzDecoder::new(&compressed[..]);
    let mut decompressed = String::new();
    std::io::Read::read_to_string(&mut decoder, &mut decompressed).expect("解压缩失败");
    decompressed
}

// 压缩二进制数据
#[wasm_bindgen]
pub fn compress_bytes(input: &[u8], level: u32) -> Vec<u8> {
    let mut encoder = GzEncoder::new(Vec::new(), Compression::new(level));
    encoder.write_all(input).expect("压缩失败");
    encoder.finish().expect("压缩失败")
}

// 解压缩二进制数据
#[wasm_bindgen]
pub fn decompress_bytes(input: &[u8]) -> Vec<u8> {
    let mut decoder = flate2::read::GzDecoder::new(&input[..]);
    let mut decompressed = Vec::new();
    std::io::Read::read_to_end(&mut decoder, &mut decompressed).expect("解压缩失败");
    decompressed
}
6.2.2 编写 Node.js 代码

在 node.js 文件中添加数据压缩的代码:

const fs = require('fs');
const path = require('path');
const wasmPath = path.join(__dirname, 'pkg', 'rust_wasm_demo.js');
const wasmModule = require(wasmPath);

async function run() {
    // 初始化 Wasm 模块
    await wasmModule.default();
    console.log('Rust WebAssembly 模块初始化成功');

    // 测试字符串压缩
    const testString = 'Hello, this is a test string for data compression! '.repeat(1000);
    const compressedString = wasmModule.compress_string(testString, 9);
    const decompressedString = wasmModule.decompress_string(compressedString);
    console.log(`字符串压缩前大小:${testString.length} bytes`);
    console.log(`字符串压缩后大小:${compressedString.length} bytes`);
    console.log(`字符串压缩率:${((1 - compressedString.length / testString.length) * 100).toFixed(2)}%`);
    console.log(`字符串解压缩成功:${decompressedString === testString}`);

    // 测试二进制数据压缩
    const testFile = path.join(__dirname, 'test.txt');
    const testData = fs.readFileSync(testFile);
    const compressedData = wasmModule.compress_bytes(testData);
    const decompressedData = wasmModule.decompress_bytes(compressedData);
    console.log(`二进制数据压缩前大小:${testData.length} bytes`);
    console.log(`二进制数据压缩后大小:${compressedData.length} bytes`);
    console.log(`二进制数据压缩率:${((1 - compressedData.length / testData.length) * 100).toFixed(2)}%`);
    console.log(`二进制数据解压缩成功:${Buffer.compare(decompressedData, testData) === 0}`);

    // 写入压缩后的文件
    const compressedFile = path.join(__dirname, 'test.txt.gz');
    fs.writeFileSync(compressedFile, Buffer.from(compressedData));
    console.log(`压缩后的文件已写入:${compressedFile}`);
}
run();

七、常见问题与解决方案

7.1 未正确释放 Wasm 分配的内存

问题现象:浏览器/Node.js 的内存使用率过高,导致页面卡顿或程序崩溃。

解决方案:

  1. 手动释放 Rust 分配的内存:使用 wasm_bindgen::prelude::JsValue::free 或 Box::into_raw。
  2. 使用智能指针:使用 Box<T> 或 Arc<T> 自动管理内存。
  3. 避免内存泄漏:在异步任务中确保所有分配的内存都被释放。
7.2 数据类型转换时的边界检查

问题现象:数据溢出,导致计算结果错误。

解决方案:

  1. 使用 std::num::Wrapping 类型处理溢出。
  2. 在转换前进行边界检查:使用 i32::try_from 或 u32::try_into。
  3. 使用 serde_wasm_bindgen 的 Deserialize trait,它会自动处理边界检查。
7.3 Wasm 模块加载失败

问题现象:浏览器控制台报错'WebAssembly.instantiateStreaming failed'或'Uncaught (in promise) TypeError: Failed to fetch'。

解决方案:

  1. 检查 Wasm 模块的路径是否正确。
  2. 确保 Webpack/Vite 的 Wasm loader 配置正确。
  3. 检查浏览器是否支持 Wasm(现代浏览器默认已支持,但需检查浏览器版本)。

八、总结与展望

8.1 总结

✅ 理解了 WebAssembly 基础:深入掌握了 WebAssembly 的核心定义、运行机制、与 JavaScript 的性能对比。 ✅ 掌握了 Rust 到 Wasm 的编译:熟练使用 wasm-pack 工具链,完成了 Rust 代码到 Wasm 模块的编译、打包、优化。 ✅ 精通了 Rust 与 JavaScript 交互:实现了双向交互(Rust 调用 JS 函数、JS 调用 Rust 函数),处理了复杂数据类型,管理了内存。 ✅ 开发了真实 Wasm 应用:编写了浏览器端高性能 Canvas 图像滤镜、Node.js 端计算密集型数据压缩任务。 ✅ 优化了 Wasm 模块:学习了使用 wasm-opt 工具优化 Wasm 体积。 ✅ 部署了 Wasm 应用:学习了部署到静态资源服务器、CDN,使用 Vite/Webpack 集成到 Vue/React 项目。

8.2 展望

下一篇文章,我们将深入学习 Rust 的嵌入式开发,包括使用 Rust 开发 Arduino、Raspberry Pi 等嵌入式设备,学习 GPIO 操作、传感器数据读取、通信协议(如 I2C、SPI),通过这些知识我们将能够将 Rust 代码运行在资源受限的嵌入式设备上。

目录

  1. Rust 与 WebAssembly 深度实战:在浏览器与 Node.js 中运行高性能代码
  2. 一、学习目标与重点
  3. 1.1 学习目标
  4. 1.2 学习重点
  5. 二、WebAssembly 基础
  6. 2.1 WebAssembly 的定义与特点
  7. 2.2 Wasm 与 JavaScript 的性能对比
  8. 2.3 Wasm 的使用场景
  9. 三、Rust 到 Wasm 的编译工具链
  10. 3.1 安装 wasm-pack
  11. 使用 Cargo 安装
  12. 3.2 安装 cargo-web
  13. 使用 Cargo 安装(需要 Rust 1.70 或更高版本)
  14. 四、Rust 与 JavaScript 交互基础
  15. 4.1 创建一个简单的 Wasm 项目
  16. 创建项目
  17. 4.2 编写 Rust 代码
  18. 4.3 编译 Wasm 模块
  19. 编译为 npm 包
  20. 4.4 编写 HTML 与 JavaScript 代码
  21. 4.5 运行 Wasm 应用
  22. 初始化 Vite 项目
  23. 安装依赖
  24. 运行开发服务器
  25. 五、Rust 与 JavaScript 交互进阶
  26. 5.1 处理复杂数据类型
  27. 5.1.1 安装依赖
  28. 5.1.2 编写 Rust 代码
  29. 5.1.3 编写 JavaScript 代码
  30. 5.2 异步交互
  31. 5.2.1 安装依赖
  32. 5.2.2 编写 Rust 代码
  33. 5.2.3 编写 JavaScript 代码
  34. 5.3 DOM 操作
  35. 5.3.1 安装依赖
  36. 5.3.2 编写 Rust 代码
  37. 六、真实案例应用
  38. 6.1 案例 1:浏览器端高性能 Canvas 图像滤镜
  39. 6.1.1 编写 Rust 代码
  40. 6.1.2 编写 HTML 与 JavaScript 代码
  41. 6.2 案例 2:Node.js 端计算密集型任务——数据压缩
  42. 6.2.1 编写 Rust 代码
  43. 6.2.2 编写 Node.js 代码
  44. 七、常见问题与解决方案
  45. 7.1 未正确释放 Wasm 分配的内存
  46. 7.2 数据类型转换时的边界检查
  47. 7.3 Wasm 模块加载失败
  48. 八、总结与展望
  49. 8.1 总结
  50. 8.2 展望
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 命名空间(namespace)详解与实战指南
  • RxJava 迁移至 Kotlin Flow 的背压策略对比与实现
  • 本地代码文件夹上传至 GitHub 详细步骤与命令集合
  • OpenClaw 多飞书机器人配置与绑定指南
  • 主流 AIGC 检测降重工具实测对比与避坑指南
  • OpenClaw 开源 AI Agent 框架技术解析与实践
  • Java 数据类型、运算符与方法核心要点总结
  • Rust与WebAssembly深度实战:浏览器与Node.js 高性能应用
  • 使用 Python 和 ChromaDB 构建简易 RAG 应用
  • 基于微信小程序 SSM 的校园顺路代送系统设计与实现
  • Ubuntu Edgy Eft 完整软件源列表配置指南
  • 在安卓设备使用 Termux 搭建 Debian 环境并运行 PC 级 Linux 应用
  • Vue 自定义指令核心原理与实战
  • 通义万相 2.1 结合智算平台:AIGC 创作实践指南
  • Python 基础:错误与异常处理详解
  • 初阶数据结构:顺序表
  • 8 卡 RTX 5090 服务器 llama.cpp 编译及多 GPU 推理实战
  • Midjourney Imagine API 申请与实战指南
  • VS Code 跨平台安装与配置优化指南
  • 基于 AnythingtoRealCharacters2511 的 ACG 真人化素材自动化方案

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online