首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >44-Rust 教程 - WASM 实践

44-Rust 教程 - WASM 实践

作者头像
LarryLan
发布2026-06-30 14:58:07
发布2026-06-30 14:58:07
1070
举报

WASM 实践

让 Rust 跑在浏览器里:wasm-bindgen 和前端交互

🎬 引入

你有没有想过,用 Rust 写前端代码?

不是开玩笑,Rust 真的可以编译成 WebAssembly (WASM),在浏览器里运行!而且性能接近原生代码。

WASM 能做什么?

  • 🎮 游戏(Unity、Unreal 引擎)
  • 📊 数据处理(图像处理、视频编码)
  • 🔐 加密算法
  • 📱 跨平台应用
  • 🧮 计算密集型任务

今天咱们就聊聊怎么用 Rust 写 WASM,和 JavaScript 交互,让 Rust 跑在浏览器里。

📌 核心概念

什么是 WebAssembly?

WASM 是一种二进制指令格式,可以在浏览器里运行。它的特点:

  • 接近原生的性能
  • 安全的沙箱环境
  • 和 JavaScript 互操作
  • 跨平台

生活化类比:

想象浏览器是个厨房:

  • JavaScript = 厨师(灵活,但有时慢)
  • WASM = 料理机(专门做计算,快)
  • 两者配合,做出美味佳肴

Rust + WASM 的工具链

工具

作用

wasm-pack

构建和发布 WASM 包

wasm-bindgen

Rust 和 JS 互操作

wasm-opt

优化 WASM 二进制

cargo-generate

快速创建项目模板

wasm-bindgen 的核心

代码语言:javascript
复制
use wasm_bindgen::prelude::*;

// 导出给 JavaScript 用
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// 使用 JavaScript 的 API
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    log(&format!("Hello, {}!", name));
}

💻 代码示例

基础示例:创建 WASM 项目

代码语言:javascript
复制
# 安装 wasm-pack
cargo install wasm-pack

# 创建项目
cargo generate --git https://github.com/rustwasm/wasm-pack-template
# 项目名称:my-wasm-app

# 构建
wasm-pack build --target web

项目结构:

代码语言:javascript
复制
my-wasm-app/
├── Cargo.toml
├── src/
│   └── lib.rs
├── pkg/              # 构建输出
│   ├── my_wasm_app.js
│   ├── my_wasm_app_bg.wasm
│   └── package.json
└── index.html

简单的数学函数

代码语言:javascript
复制
// src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
         => ,
         => ,
        _ => {
            let mut a = ;
            let mut b = ;
            for _ in ..=n {
                let temp = a + b;
                a = b;
                b = temp;
            }
            b
        }
    }
}

#[wasm_bindgen]
pub fn factorial(n: u32) -> u32 {
    (..=n).product()
}

使用(HTML + JavaScript):

代码语言:javascript
复制
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Rust WASM Demo</title>
</head>
<body>
    <script type="module">
        import init, { add, fibonacci, factorial } from './pkg/my_wasm_app.js';
        
        async function run() {
            await init();
            
            console.log('2 + 3 =', add(, ));
            console.log('fibonacci(10) =', fibonacci());
            console.log('factorial(5) =', factorial());
        }
        
        run();
    </script>
</body>
</html>

操作 DOM

代码语言:javascript
复制
// Cargo.toml
[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Window"] }

// src/lib.rs
use wasm_bindgen::prelude::*;
use web_sys::window;

#[wasm_bindgen]
pub fn change_background_color(color: &str) {
    let document = window()
        .expect("没有 window")
        .document()
        .expect("没有 document");
    
    let body = document.body().expect("没有 body");
    body.set_attribute("style", &format!("background-color: {}", color))
        .expect("设置样式失败");
}

#[wasm_bindgen]
pub fn alert_message(message: &str) {
    if let Some(window) = window() {
        window.alert_with_message(message).ok();
    }
}

和 JavaScript 互操作

代码语言:javascript
复制
// src/lib.rs
use wasm_bindgen::prelude::*;

// 导入 JavaScript 函数
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = Math)]
    fn random() -> f64;
    
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen]
pub fn random_number() -> f64 {
    random()
}

#[wasm_bindgen]
pub fn log_to_console(message: &str) {
    log(message);
}

// 回调函数
#[wasm_bindgen]
pub fn call_js_callback(callback: &js_sys::Function, value: i32) -> Result<(), JsValue> {
    callback.call1(&JsValue::NULL, &JsValue::from(value))
}

JavaScript 端:

代码语言:javascript
复制
import init, { call_js_callback } from './pkg/my_wasm_app.js';

async function run() {
    await init();
    
    // Rust 调用 JS 回调
    const myCallback = (value) => {
        console.log('从 Rust 收到的值:', value);
    };
    
    call_js_callback(myCallback, );
}

处理字符串和数组

代码语言:javascript
复制
// src/lib.rs
use wasm_bindgen::prelude::*;
use js_sys::Array;

#[wasm_bindgen]
pub fn reverse_string(s: &str) -> String {
    s.chars().rev().collect()
}

#[wasm_bindgen]
pub fn sort_numbers(numbers: Vec<i32>) -> Vec<i32> {
    let mut sorted = numbers;
    sorted.sort();
    sorted
}

#[wasm_bindgen]
pub fn process_array(arr: &[i32]) -> Vec<i32> {
    arr.iter().map(|&x| x * ).collect()
}

// 返回复杂数据
#[wasm_bindgen]
pub fn get_user_info() -> JsValue {
    let obj = js_sys::Object::new();
    js_sys::Reflect::set(&obj, &JsValue::from("name"), &JsValue::from("Alice")).unwrap();
    js_sys::Reflect::set(&obj, &JsValue::from("age"), &JsValue::from()).unwrap();
    obj
}

图片处理实战

代码语言:javascript
复制
// Cargo.toml
[dependencies]
wasm-bindgen = "0.2"
image = "0.24"

// src/lib.rs
use wasm_bindgen::prelude::*;
use image::{ImageBuffer, Rgb};

#[wasm_bindgen]
pub fn create_grayscale_image(width: u32, height: u32, data: &[u8]) -> Vec<u8> {
    let mut img = ImageBuffer::<Rgb<u8>, Vec<u8>>::new(width, height);
    
    for (x, y, pixel) in img.enumerate_pixels_mut() {
        let index = ((y * width + x) * ) as usize;
        let r = data[index];
        let g = data[index + ];
        let b = data[index + ];
        
        // 转灰度
        let gray = (r as u32 *  + g as u32 *  + b as u32 * ) / ;
        *pixel = Rgb([gray as u8, gray as u8, gray as u8]);
    }
    
    img.into_raw()
}

#[wasm_bindgen]
pub fn apply_sepia(data: &[u8], width: u32, height: u32) -> Vec<u8> {
    let mut result = Vec::with_capacity(data.len());
    
    for i in (..data.len()).step_by() {
        let r = data[i] as f32;
        let g = data[i + ] as f32;
        let b = data[i + ] as f32;
        
        let new_r = (0.393 * r + 0.769 * g + 0.189 * b).min(255.0) as u8;
        let new_g = (0.349 * r + 0.686 * g + 0.168 * b).min(255.0) as u8;
        let new_b = (0.272 * r + 0.534 * g + 0.131 * b).min(255.0) as u8;
        
        result.push(new_r);
        result.push(new_g);
        result.push(new_b);
    }
    
    result
}

🐛 常见坑点

坑点 1:字符串编码问题

代码语言:javascript
复制
// ❌ 直接返回 &str 不行
#[wasm_bindgen]
pub fn get_string() -> &str {
    "hello"// 编译错误
}

// ✅ 返回 String
#[wasm_bindgen]
pub fn get_string() -> String {
    String::from("hello")
}

坑点 2:错误处理

代码语言:javascript
复制
// ❌ Result 需要特殊处理
#[wasm_bindgen]
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b ==  {
        Err("除数不能为零".to_string())
    } else {
        Ok(a / b)
    }
}

// JavaScript 端需要 try-catch
try {
    const result = divide(, );
} catch (e) {
    console.error(e);
}

坑点 3:内存泄漏

代码语言:javascript
复制
// ❌ 忘记释放
#[wasm_bindgen]
pub fn process_data(data: &[u8]) -> Vec<u8> {
    // 大量分配
}

// ✅ 使用 Box 和手动释放
#[wasm_bindgen]
pub struct DataProcessor {
    data: Vec<u8>,
}

#[wasm_bindgen]
impl DataProcessor {
    pub fn new() -> DataProcessor {
        DataProcessor { data: Vec::new() }
    }
    
    pub fn process(&mut self, input: &[u8]) {
        self.data = input.to_vec();
        // 处理...
    }
    
    pub fn free(self) {
        // 显式释放
    }
}

坑点 4:异步支持

代码语言:javascript
复制
// ❌ 早期 wasm-bindgen 不支持 async
// ✅ 现在支持了
#[wasm_bindgen]
pub async fn fetch_data(url: &str) -> Result<JsValue, JsValue> {
    // 使用 web-sys 的 fetch API
    let window = web_sys::window().unwrap();
    let opts = web_sys::RequestInit::new();
    // ...
}

🎯 实战案例

案例 1:Markdown 解析器

代码语言:javascript
复制
// Cargo.toml
[dependencies]
wasm-bindgen = "0.2"
pulldown-cmark = "0.9"

// src/lib.rs
use wasm_bindgen::prelude::*;
use pulldown_cmark::{Parser, Options, html};

#[wasm_bindgen]
pub fn markdown_to_html(markdown: &str) -> String {
    let mut options = Options::empty();
    options.insert(Options::ENABLE_STRIKETHROUGH);
    options.insert(Options::ENABLE_TABLES);
    
    let parser = Parser::new_ext(markdown, options);
    
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);
    
    html_output
}

案例 2:密码生成器

代码语言:javascript
复制
// Cargo.toml
[dependencies]
wasm-bindgen = "0.2"
rand = "0.8"
getrandom = { version = "0.2", features = ["js"] }

// src/lib.rs
use wasm_bindgen::prelude::*;
use rand::Rng;

#[wasm_bindgen]
pub fn generate_password(length: usize, use_uppercase: bool, use_numbers: bool, use_symbols: bool) -> String {
    let mut chars = String::from("abcdefghijklmnopqrstuvwxyz");
    
    if use_uppercase {
        chars.push_str("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
    }
    if use_numbers {
        chars.push_str("0123456789");
    }
    if use_symbols {
        chars.push_str("!@#$%^&*()_+-=[]{}|;:,.<>?");
    }
    
    let mut rng = rand::thread_rng();
    (..length)
        .map(|_| {
            let idx = rng.gen_range(..chars.len());
            chars.chars().nth(idx).unwrap()
        })
        .collect()
}

🧠 思维导图

44-WASM 实践
44-WASM 实践

📝 小结

金句回顾:

  1. WASM 让 Rust 跑在浏览器——性能接近原生
  2. wasm-bindgen 是桥梁——Rust 和 JS 互操作
  3. web-sys 提供 Web API——DOM、fetch、console
  4. 注意内存管理——WASM 内存不会自动回收
  5. 适合计算密集型任务——别用 WASM 做简单 DOM 操作

下篇预告:

性能优化是每个程序员都要面对的课题。下篇聊聊Rust 性能优化,看看怎么用基准测试、profiling、零成本抽象,让你的代码飞起来!

🔗 参考资料

  • wasm-pack
  • wasm-bindgen
  • WebAssembly
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • WASM 实践
    • 🎬 引入
    • 📌 核心概念
      • 什么是 WebAssembly?
      • Rust + WASM 的工具链
      • wasm-bindgen 的核心
    • 💻 代码示例
      • 基础示例:创建 WASM 项目
      • 简单的数学函数
      • 操作 DOM
      • 和 JavaScript 互操作
      • 处理字符串和数组
      • 图片处理实战
    • 🐛 常见坑点
      • 坑点 1:字符串编码问题
      • 坑点 2:错误处理
      • 坑点 3:内存泄漏
      • 坑点 4:异步支持
    • 🎯 实战案例
      • 案例 1:Markdown 解析器
      • 案例 2:密码生成器
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档