Rust WebAssembly Demo
-
确保已安装
rustup、rustc和cargo -
安装 wasm-pack
-
cargo install cargo-generatecargo-generate 通过利用预先存在的 git 存储库作为模板,帮助您快速启动和运行新的 Rust 项目。- 提示
Try re-running 'cargo install' with '--locked'时按提示操作 - 注意
cargo-generate对rustc版本的要求,可以像 npm 包那样安装指定版本
- 提示
-
cargo generate --git https://github.com/rustwasm/wasm-pack-template初始化一个 wasm 项目- 访问不到 github.com 时可以将库下载到本地
cargo generate --path ./wasm-pack-template-master
├── Cargo.toml # package.json └── src ├── lib.rs # 项目入口 ├── utils.rsextern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn greet(name: &str) -> String { format!("Hello - {}!", name) } - 访问不到 github.com 时可以将库下载到本地
-
wasm-pack build构建pkg/ ├── package.json ├── README.md ├── hello_wasm_bg.js # 由wasm-bindgen生成,包含JavaScript胶水,用于将DOM和JavaScript函数导入Rust,并将wasm API暴露到js中。 ├── hello_wasm_bg.wasm ├── hello_wasm_bg.wasm.d.ts ├── hello_wasm.d.ts └── hello_wasm.js # 有问题,不能直接运行// hello_wasm.js 有问题,不能直接运行 import * as wasm from './hello_wasm_bg.wasm' export * from './hello_wasm_bg.js' import { __wbg_set_wasm } from './hello_wasm_bg.js' __wbg_set_wasm(wasm)但在实测时需要
<script type="module"> import { __wbg_set_wasm, __wbg_alert_ca25c76a89dd1181, greet } from '/js/official_demo_bg.js' const importObject = { './official_demo_bg.js': { __wbg_alert_ca25c76a89dd1181 } } // 如果 wasm 不依赖 dom 原生方法,比如 alert,则不需要 importObject WebAssembly.instantiateStreaming(fetch('/js/official_demo_bg.wasm'), importObject) .then((obj) => obj.instance.exports) .then((wasm) => __wbg_set_wasm(wasm)) .then(() => { // 使用你的WebAssembly实例 greet() }) </script>在 VUE 中 可以
// App.vue loadRustWasm( 'official_demo_bg', import('@/pkg/official_demo_bg.wasm?init'), import('@/pkg/official_demo_bg.js') ).then(wasm => { wasm.greet() }) // loadRustWasm.js export default function loadRustWasm<T>( wasmName: string, importWasm: Promise<typeof import('*.wasm?init')>, importBg: Promise<T> ) { return Promise.all([importWasm, importBg]).then(([{ default: init }, bg]) => { let importObject = {} Object.keys(bg).forEach(key => { if (key !== '__wbg_set_wasm' && key.startsWith('__wb')) { importObject[key] = bg[key] } }) if (Object.keys(importObject).length) { importObject = { ['./' + wasmName + '.js']: importObject } } else { importObject = undefined } return init(importObject).then(instance => { // @ts-ignore bg.__wbg_set_wasm(instance.exports) return instance.exports as T }) }) }
参数传递
-
简单值会被拷贝,如 number、string、boolean
-
ArrayBuffer、Uint8Array 可以直接写入/读取 WebAssembly 线性内存空间
-
是共享内存,不是句柄转移,与 Worker 不同。所以 wasm 收到是个只读的借用
-
如果 ArrayBuffer、Uint8Array 由 js 创建,则 V8 GC 会正常工作。 如果 buffer 是由 WebAssembly 创建/分配,由需要由 WebAssembly 手动进行内存回收
-
-
序列化和反序列化会造成额外的性能、内存开销,尽量减少使用
一个好的 JavaScript↔WebAssembly 接口设计通常是将大型、长寿命的数据结构实现为 WebAssembly 线性内存中的 Rust 类型。
使用包 js-sys 在 Rust 中创建 js 对象
rust 的数据类型比 js 更宽泛,很多都是 js 不支持的。 js-sys 包提供了
- 一些转换函数
return js_sys::Uint8Array::from(u8Array.as_slice()) - 一些 js 原生方法
js_sys::Math::random()
将 Rust 数组转换成 JS Uint8Array,且在 rust 函数结束时 u8Array 占用的内存会被释放。因为它将数据复制到 WASM 线性内存中供 JS 使用,JS 获取的是标准的 Uint8Array,V8 GC 会自动回收。
测试、调试、日志
单元测试 wasm-pack test --chrome --headless
# Cargo.toml 中添加
[lib]
name = "hello_wasm"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = { version = "0.1.7", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
// tests/web.rs
#![cfg(target_arch = "wasm32")]
extern crate wasm_bindgen_test;
use std::print;
use wasm_bindgen_test::*;
extern crate hello_wasm;
use hello_wasm::conway_inner;
wasm_bindgen_test_configure!(run_in_browser);
#[cfg(test)]
pub fn input_spaceship(size: usize) -> Vec<u8> {
let mut universe = vec![0u8; size * size];
universe[7] = 1;
universe[12] = 1;
universe[17] = 1;
universe
}
#[cfg(test)]
pub fn expected_spaceship(size: usize) -> Vec<u8> {
let mut universe = vec![0u8; size * size];
universe[11] = 1;
universe[12] = 1;
universe[13] = 1;
universe
}
#[wasm_bindgen_test]
fn pass() {
let size = 5;
let input_universe = input_spaceship(size);
let res_conway = conway_inner(size, &input_universe);
let expected_universe = expected_spaceship(size);
print!("input: {:?}\n", res_conway);
print!("output: {:?}\n", expected_universe);
// 判断结果与预期是否一致
assert_eq!(res_conway, expected_universe);
}
将调试日志发送到 console
# Cargo.toml 中添加
[dependencies.web-sys]
version = "0.3"
features = ["console"]
extern crate web_sys;
// A macro to provide `println!(..)`-style syntax for `console.log` logging.
macro_rules! log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into());
}
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
log!("打印到浏览器 console 控制台:{}", name);
format!("Hello - {}!", name)
}