关注

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

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

在这里插入图片描述

一、学习目标与重点

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)。

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" id="todo-input" placeholder="添加待办事项...">
        <button id="todo-add-button">添加</button>
        <div id="todo-list"></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 class="todo-remove-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::freeBox::into_raw
  2. 使用智能指针:使用Box<T>Arc<T>自动管理内存
  3. 避免内存泄漏:在异步任务中确保所有分配的内存都被释放

7.2 数据类型转换时的边界检查

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

解决方案

  1. 使用std::num::Wrapping类型处理溢出
  2. 在转换前进行边界检查:使用i32::try_fromu32::try_into
  3. 使用serde_wasm_bindgenDeserialize 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代码运行在资源受限的嵌入式设备上。

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/COLLINSXU/article/details/157517001

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--