Rust WebAssembly

7 阅读3分钟

Rust WebAssembly Demo

  1. 确保已安装rustuprustccargo

  2. 安装 wasm-pack

  3. cargo install cargo-generate cargo-generate 通过利用预先存在的 git 存储库作为模板,帮助您快速启动和运行新的 Rust 项目。

    • 提示 Try re-running 'cargo install' with '--locked' 时按提示操作
    • 注意 cargo-generaterustc 版本的要求,可以像 npm 包那样安装指定版本
  4. 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.rs
    
    extern crate wasm_bindgen;
    
    use wasm_bindgen::prelude::*;
    
    #[wasm_bindgen]
    pub fn greet(name: &str) -> String {
        format!("Hello - {}!", name)
    }
    
  5. 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
         })
       })
    }
    

rustwasm 官方教程

参数传递

  • 简单值会被拷贝,如 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 会自动回收。

crates.io/crates/js-s…

rustwasm.github.io/wasm-bindge…

测试、调试、日志

单元测试 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)
}

rustwasm.github.io/docs/book/r…