使用 Rust 编写 WebAssembly

参考:《编译 Rust 为 WebAssembly》
https://developer.mozilla.org/zh-CN/docs/WebAssembly/Rust_to_Wasm

一、环境搭建

(一)安装工具链

1、wasm-bindgen

Facilitating high-level interactions between Wasm modules and JavaScript
https://github.com/rustwasm/wasm-bindgen

# 使用 Cargo 安装
cargo install wasm-bindgen-cli

文档:
Introduction - The wasm-bindgen Guide
https://rustwasm.github.io/wasm-bindgen/introduction.html
https://rustwasm.github.io/

介绍- The wasm-bindgen Guide
https://llever.com/wasm-bindgen/

2、wasm-pack

Your favorite rust -> wasm workflow tool!
https://github.com/rustwasm/wasm-pack

# 使用 Cargo 安装
cargo install wasm-pack
# 使用 Homebrew 安装
brew install wasm-pack
3、wasm-tools (可选)

Low level tooling for WebAssembly in Rust
https://github.com/bytecodealliance/wasm-tools

# 使用 Cargo 安装
cargo install wasm-tools
# 使用 Homebrew 安装
brew install wasm-tools
4、binaryen (包含 wasm-opt,可选)

Optimizer and compiler/toolchain library for WebAssembly
https://github.com/WebAssembly/binaryen

# 使用 Homebrew 安装
brew install binaryen
# 通过 Cargo 安装 Rust 版 wasm-opt
cargo install wasm-opt

(二)添加平台支持

增加对 WebAssembly 的支持:

# 查看当前 Rust 环境支持构建的平台
rustup target list
# 添加 WebAssembly 的支持
rustup target add wasm32-unknown-unknown

二、创建工程

(一)手工创建

1、创建 Rust 库工程
cargo new --lib hello-wasm
2、编写代码

清理掉 src/lib.rs 中的内容,然后写入以下内容:

extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}
  • 通过 #[wasm_bindgen] 来指定与外部 JavaScript 进行交互。
    • extern 用于通过 Rust 调用外部的 JavaScript 代码
#[wasm_bindgen]
extern {
    pub fn alert(s: &str);
}
  • 供外部 JavaScript 调用的 Rust 代码
#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}
3、配置 Cargo.toml
[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

(二)使用 wasm-pack 工具创建

创建工程

wasm-pack new

三、构建 WebAssembly

(一)手工构建

# 使用 Cargo 构建
cargo build --target wasm32-unknown-unknown --release

构建结果在这里:

<工程目录>/target/wasm32-unknown-unknown/release/<工程名>.wasm

(二)使用 wasm-pack 工具构建

构建工程

wasm-pack build -t web

构建结果在这里:

<工程名目录>/pkg/

四、踩坑

(一).wasm 文件与 JavaScript 的关系

从本质上说,JavaScript 可以通过 Ajax 将 .wasm 文件加载为二进制流,然后调用 WebAssembly 对象加载这个二进制流,从而完成对 .wasm 文件的加载,并完成初始化。这时已经可以通过 JavaScript 调用 .wasm 文件中的方法了。

如果要在 Rust 中支持这种方式,那么需要按如下方式编写函数:

// 使用 #[no_mangle] 防止名称修饰,使函数名可与其他语言的接口匹配
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

而且这种方式不需要什么三方依赖。通过 rustup target list 命令查看支持 wasm32-unknown-unknown 这个 target 就可以。
几种方式示例:https://github.com/Herbert8/simple-webassembly-demo/blob/main/web/utils.js

这种直接编写直接加载的方式,直观简单。缺点是对于复杂数据类型(如:字符串)的操作比较繁琐,需要自己在 JavaScript 中增加很多额外处理。这样的话,在组建复杂度比较高的场景,编写的 WebAssembly 难以作为成形的组件使用。
所以一般使用 Rust 编写的 WebAssembly 组件,都是结合着前端框架一起提供,便于使用。这也就是为什么 Rust 开发 WebAssembly 的教程都会涉及到 npm(这是很好的做法,但并不是必需的)。

(二)工具链在开发过程中的作用

根据前面的描述,在 WebAssembly 开发的过程中,除了业务代码,基础框架(包括 Rust 和 JavaScript)的工作多且繁琐,靠人工创建工程的方式效率太低,这个问题可以引入 wasm-pack 这个工具来解决。对于用于生产的系统,或者复杂的场景,不建议手工打造了,还是采用 wasm-pack,能帮我们做以下事情:
1、创建工程

wasm-pack new my_proj

会根据一个 WebAssembly 的工程模板进行工程创建,包括基础代码和配置,省去手工配置的繁琐。

2、工程的构建

wasm-pack build -t web
# 或
wasm-pack build --target web

这里指定 target 非常重要!!!因为在 build 的时候,除了生成 WebAssembly,还会生成配套的 JavaScript 包装,以便于外部调用。当没有明确指定 target 时会使用 bundler,这种方式不适合 Web,使用时报错。

3、wasm-pack 也会与前面提及的其他工具链集成,同时完成代码优化、瘦身以及包装文件的生成。

(三)包装文件的使用

使用 wasm-pack 构建后,除了 .wasm 文件,还会生成其对应的 JavaScript 包装文件。这里说下包装文件的使用规则。
1、包装文件使用了 ES6 模块机制
2、包装文件默认导出的是它的初始化器
3、需要在初始化完成后,再调用自己的方法

// 导入 js 中提供的函数
import init, { my_func1 as myFunc1, my_func2 as myFunc2 } from 'my_proj.js'
// 初始化(异步执行,通过 await 等待,必要时做好相关异常处理)
await init()
myFunc1()
myFunc2()

(四)开发工作流的优化

采用 VS Code 进行开发是不错的选择,能编写 Rust、配置文件以及前端代码。但每次修改后编译、将 .wasm 及包装文件部署到 Web 服务器,修改前端代码后也需要部署,还是有些繁琐。
这里推荐使用 make,自行编写 Makefile,VS Code 内置了 Makefile 的支持。可以根据自己的需要编写 Makefile,根据自己要做的事情定义 Target,配合 VS Code 自定义快捷键的功能,感觉整个开发工作流没那么繁琐了。
大家有什么好的建议请回复。

五、遗留问题

Debug 方面没找到好办法,还在研究中。有了新发现随时补充上来。

(完)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容