Rust + WebAssembly 新手完全入门指南

Rust + WebAssembly 新手完全入门指南

这篇文章面向前端、Rust 开发者,只要跟着步骤就能跑通你的第一个 WebAssembly 前端组件。

WebAssembly 是什么

WebAssembly(简称 Wasm)是一种可在现代浏览器中运行的低级、紧凑、高效的二进制指令格式。它的出现主要是用来解决 JavaScript 天生的性能瓶颈,尤其是计算密集型场景下,WebAssembly 能达到接近原生的执行性能。

可以说,WebAssembly 的出现,使得在网页上运行高级、复杂的客户端应用(如图像编辑、Web AI 推理、复杂的游戏引擎)成为可能。

通过 caniuse,我们可以看到 WebAssembly 在主流浏览器中已经广泛可用,如下图所示:

环境准备

首先,确保你已经安装好 Nodejs 和 Rust 环境。这里我们会用到 wasm-packwasm-pack 是 Rust Wasm 开发的核心工具,负责构建、打包、测试 Wasm 项目。

shell 复制代码
# 安装 wasm-pack
cargo install wasm-pack

接下来,我们使用 wasm-pack 初始化项目:

shell 复制代码
# 初始化项目
wasm-pack new wasm-example
cd wasm-example

wasm-pack 会帮我们生成如下的目录结构:

shell 复制代码
.
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
├── src
│   ├── lib.rs
│   └── utils.rs
└── tests
    └── web.rs

wasm-pack 初始化项目时默认使用的是 wasm-pack-template 这个模板,由于缺少维护,所以默认使用的 Editionwasm-bindgen 等的版本都比较低,你可以手动调整下,但是在这个案例中,我们就暂时不动它们了。

在以后的开发中,你可以维护一个自己的模板,然后通过如下命令使用模板:

shell 复制代码
wasm-pack new <name> --template <template>

实现一个简单 Wasm 组件

我们首先实现一个简单 Wasm 组件,我们将会用到 js-sys 这个库,它包含 JavaScript 语言本身的 API,如数组、字符串、console、Promise 等。

shell 复制代码
# 添加 js-sys
cargo add js-sys

现在我们直接修改 src/lib.rs 文件:

rust 复制代码
# 默认模板里有 src/utils.rs,这里引入了它
mod utils;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn sum_array(arr: &js_sys::Array) -> u32 {
    let mut sum = 0;

    for i in 0..arr.length() {
        let val = arr.get(i).as_f64().unwrap_or(0.0) as u32;
        sum += val;
    }

    sum
}

#[wasm_bindgen] 属性宏声明了将导出 sum_array 函数以供 JS 调用,这就是我们这个简单组件提供的能力。接下来,我们进行编译:

shell 复制代码
wasm-pack build --target web

编译完成后,我们可以在 pkg 目录中看到编译结果:

shell 复制代码
pkg
├── package.json
├── README.md
├── wasm_example_bg.js
├── wasm_example_bg.wasm
├── wasm_example_bg.wasm.d.ts
├── wasm_example.d.ts
└── wasm_example.js
  • wasm_example_bg.wasm 是核心的 Wasm 二进制文件,包含我们编写的 Rust 逻辑;
  • wasm_example.js 是 JS 胶水代码,负责 Wasm 的加载、类型转换,把 Rust 函数封装成 JS 可直接调用的方法;
  • wasm_example.d.ts 是 TypeScript 类型定义,用于支持 TS 项目。

这里的 --target web 表示我们的目标编译平台是 web,这样方便在 HTML 文件中引入。在根目录创建 index.html 并编辑:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Rust + WebAssembly</title>
</head>
<body>
  <script type="module">
    import init, { sum_array } from './pkg/wasm_example.js';

    await init();

    console.log("求和结果为:", sum_array([10, 20, 30]));
  </script>
</body>
</html>

由于浏览器的安全策略不允许加载本地 Wasm 模块,所以无法使用 file:// 协议打开 HTML 文件,我们需要一个本地 HTTP 服务器来启动项目。

shell 复制代码
npx serve .

这时候,打开浏览器的控制台就能够看到打印出来的求和结果了。

使用 Rust 操作 DOM

借助 web-sys,我们可以直接用 Rust 操作 DOM。与 js-sys 不同,web-sys 包含浏览器环境提供的 Web API。

shell 复制代码
cargo add web-sys --features "Window,Document,HtmlElement"

这里需要注意的是,web-sys 必须指定启用的 feature,不然编译不通过。

现在我们通过 web-sys 来修改网页标题,编辑 src/lib.rs,添加:

rust 复制代码
#[wasm_bindgen]
pub fn set_title(title: &str) {
    // 获取 window 对象
    let win = match web_sys::window() {
        Some(w) => w,
        None => return,
    };

    // 获取 document 对象
    let doc = match win.document() {
        Some(d) => d,
        None => return,
    };

    // 修改网页标题
    doc.set_title(title);
}

重新编译完成后,我们修改 index.html,添加:

html 复制代码
<script type="module">
  import init, { set_title, sum_array } from './pkg/wasm_example.js';

  await init();

  console.log("求和结果为:", sum_array([10, 20, 30]));

  set_title("Hello world");
</script>

刷新网页后,我们就能够看到网页的标题被修改为 Hello world 了。

接入 Vite

在实际开发中,我们很少直接写原生 HTML,基本上是基于 Vite、Webpack 等构建工具进行开发,这里以最常用的 Vite 为例,演示如何集成 Rust Wasm。

shell 复制代码
npm create vite@latest wasm-vite-example -- --template vanilla-ts
cd wasm-vite-example

把之前的 wasm-example 项目整个拷贝到 wasm-vite-example 的根目录。在实际开发中,这种时候肯定是要用 monorepo 的,但在这个案例中就不这么弄了,一切从简。

shell 复制代码
.
├── .gitignore
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── public
├── src
├── tsconfig.json
└── wasm-example

安装插件:

shell 复制代码
npm install -D vite-plugin-wasm-pack

创建 vite.config.ts 并编辑:

typescript 复制代码
import { defineConfig } from 'vite';
import wasmPack from 'vite-plugin-wasm-pack';

export default defineConfig({
  plugins: [wasmPack('./wasm-example')]
});

接下来,在 src/main.ts 中添加上我们之前写的业务逻辑,如下:

typescript 复制代码
import init, { set_title, sum_array } from 'wasm_example'

async function initWasm() {
  await init();

  console.log("求和结果为:", sum_array([10, 20, 30]));

  set_title("Hello world");
}

initWasm();

启动开发服务:

shell 复制代码
npm run dev

打开浏览器就能看到 Wasm 函数的执行结果了。

结尾

至此,我们就成功实现了第一个 WebAssembly 前端组件,虽然它很简陋,没有涉及到:

  • 测试与调试,如:用 wasm-pack test 进行单元测试、使用浏览器 DevTools 调试 Wasm 代码;
  • Wasm 高级特性,如共享内存、多线程、SIMD 指令等;
  • 性能优化与踩坑。

不过没有关系,这些内容在后续的文章更新中可能会涉及到。现在,我们需要做的是动手实现一遍这个简单的 Wasm 组件。毕竟对于新手而言,最好的学习方式就是动手实践。

相关推荐
Java水解6 小时前
Rust异步缓存系统的设计与实现
后端·rust
Rust研习社10 小时前
为什么错误返回在工程实践中要优于异常捕获
rust
Luna-player11 小时前
Sass与stylus的区别
rust·sass·stylus
问道飞鱼1 天前
【Tauri框架学习】Tauri 与 React 前端集成:通信机制与交互原理详解
前端·学习·react.js·rust·通信
weixin_387534222 天前
Ownership - Rust Hardcore Head to Toe
开发语言·后端·算法·rust
luffy54592 天前
Rust语言入门-变量篇
开发语言·后端·rust
好家伙VCC2 天前
# 发散创新:用 Rust构建高性能游戏日系统,从零实现事件驱动架构 在现代游戏开发中,**性能与可扩展性**是核心命题。传统基于
java·python·游戏·架构·rust
Source.Liu2 天前
【Iced】transformation.rs文件解析
rust·iced
小杍随笔2 天前
【Rust 语言编程知识与应用:闭包详解】
开发语言·后端·rust