Rust WebAssembly 101

@chikoski

PWA Night 2022/02/16

https://bit.ly/rust-wasm-101

TL;DR;

  • 既存のソフトウェア資産を使いまわせるのが WebAssembly の魅力
  • Rust は Wasm 向けのプログラムを書きやすい言語の一つ
  • 初めるなら wasm-pack を使うと良いように思う
% npm i -g wasm-pack
% wasm-pack new your_project
% cd your_project
% code .
% wasm-pack build
% wasm-pack pack

Wasm とは

  • バイナリーファイル
  • モジュール定義
    • インポート
    • エキスポート
    • 関数定義
    • データー
    • etc

JS から Wasm を利用するには

  • Wasm ファイルをインスタンス化して利用する
  • エキスポートされた関数は JS の関数と同様に扱える
const source = await fetch("wasm-module.wasm");
const imports = {
    debug: (number) => console.log(number),
};
const wasmModule = await WebAssembly.instantiateStreaming({imports}, source);

const sum = wasmModule.exports.add(1, 2);
const add = wasmModule.exports.add;
const anotherSum = add(3, 4);

利用目的 #1: エコシステム

利用目的 #2: パフォーマス

  • 常に速いというわけではない
    • ブラウザ組み込みの関数の方が速い
    • JS の方が速い場合も多い
  • 安定したパフォーマンス
    • JIT に左右されない
    • パフォーマンスが読めるというのは利点
  • 例:Zoom

利用目的 #3: セキュリティ

  • サンドボックス内での実行
    • 安全な実行コードの生成
    • メモリ保護
    • アイソレーション
  • 安全性とフレキシビリティとの良いとこどり
  • 例:
    Proxy-wasm, Shopify App, Amazon Prime Video

Wasm 向けの開発ができる言語は?

  • メジャーな言語であれば大体対応している
  • 成熟度はさまざま
    • ツール
    • バイナリーサイズ
    • API への対応
    • 仕様への対応
  • C/C++RustAssemblyScript, C# は有力な候補

Rust でのWeb アプリ開発のはじめかた

wasm-pack: プロジェクトの作成

  • wasm-pack new プロジェクト名 でプロジェクトが作成される
  • ソースコードは src フォルダにある
% wasm-pack new hello_world
[INFO]: ⬇  Installing cargo-generate...
🐑  Generating a new rustwasm project with name 'hello_world'...
⚠   Renaming project called `hello_world` to `hello-world`...
🔧   Creating project called `hello-world`...
✨   Done! New project created /Users/chikoski/talks/20220216-wasm/samples/hello-world
[INFO]: 🐑 Generated new project at /hello_world
% cd hello_world
% ls
Cargo.toml  LICENSE_APACHE  LICENSE_MIT  README.md  src  tests

エキスポートする関数の定義

  1. src/lib.rs に関数を定義する
  2. wasm-bindgen プラグマをつける
  3. pub キーワードをつける
#[wasm_bindgen]
pub fn add(a: u32, b: u32) -> u32 {
    a + b
}

ビルドと npm パッケージの作成

  • wasm-pack build でビルド
    • ビルドアーティファクトは pkg フォルダに作成される
    • Wasm ファイル以外に js, d.ts, package.json も作成される
  • wasm-pack pack で npm パッケージを作成
% wasm-pack build -t web
% wasm-pack pack

% ls pkg/*.tgz
pkg/hello-world-0.1.0.tgz

アプリへの組み込み

  • 作成した tgz ファイルを Dependencies に追加する
  • 初期化関数が default export に設定されている
  • node_modules/モジュール名/*.wasm を適切に配置する
import helloWorld, {add} from "hello-world"

async function main(){
  await helloWorld("配置した Wasm ファイルの URL");
  const sum = add(1, 2);
  console.log(`1 + 2 = ${sum}`);
}

web_sys クレート: Web API の利用

  • Wasm には標準ライブラリーが定められていない
  • つまり Web API の利用には、準備が必要
    • API をラップした関数の用意
    • インスタンス化時に import を適切に設定する
    • JS <-> Rust のデータ変換を行う
  • web_sys クレートはこれらを隠蔽する (e.g. fetch の使用)

web_sys: Cargo.toml へ依存関係を追加

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Document',
  'Element',
  'HtmlElement',
  'Node',
  'Window',
]

web_sys: hello_world

  • Window オブジェクトや Document は取得できない場合がある
  • DOM 操作は失敗する場合がある
#[wasm_bindgen]
pub fn greet() -> Result<(), JsValue> {
    let window = web_sys::window().ok_or("No Window object found")?;
    let document = window.document().ok_or("No Document object found")?;
    let body = document.body().ok_or("document.body does not exist")?;
    let message = document.create_element("div")?;
    message.set_text_content(Some("Hello, world!"));
    body.append_child(&message)?;
    Ok(())
}

DOM 操作は JS の方が簡潔に書ける

  • greet 関数を JS で書くと次のように簡潔に書ける
  • Wasm に DOM 操作をさせない方が良い?
  • JS と Wasm との役割分担は課題
    • 変更可能性と(人員を含めた)メンテナンス性
    • コードサイズとロードパフォーマンス
val message = document.createElement("div");
div.text = "Hello, world!";
document.body.appendChild(message);

例:プラグイン

  • 「同じような処理」
    • 画像の圧縮 (e.g. Squoosh)
    • データの変換
      (e.g. Wasm-proxy)
  • ロードパフォーマンスへの影響が少ない?
  • 既存資産を活かしやすい?

まとめ

  • WebAssembly はモジュール定義
  • 既存資産の再利用が最大の利点
  • wasm-pack: Rust での Wasm 開発が手軽になる
  • Wasm をどのように使うかは検討が必要
    • メンテナンス性
    • ロードパフォーマンスへの影響