WebAssemblyでちょっと分かったこと

それは、基本内部表現が整数か小数かなので、文字列やバイト列の扱いが結構大変と言うことです。

例えば以下は適当に作った、単にバイト列となるデータを受取り、新しくコピーして返すだけの関数です。

#[wasm_bindgen]
pub fn get_buffer(buf: &[u8]) -> Vec<u8> {
    let mut new_buf = Vec::new();
    for i in buf.iter() {
        new_buf.push(*i);
    }
    new_buf
}

これ、Node側でどういう操作をしなきゃいけないかというと、こうなるんですよ。 もちろん、関数の中で使ってる関数も実装が必要です。

module.exports.get_buffer = function(buf) {
    try {
        const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
        const ptr0 = passArray8ToWasm0(buf, wasm.__wbindgen_malloc);
        const len0 = WASM_VECTOR_LEN;
        wasm.get_buffer(retptr, ptr0, len0);
        var r0 = getInt32Memory0()[retptr / 4 + 0];
        var r1 = getInt32Memory0()[retptr / 4 + 1];
        var v2 = getArrayU8FromWasm0(r0, r1).slice();
        wasm.__wbindgen_free(r0, r1 * 1, 1);
        return v2;
    } finally {
        wasm.__wbindgen_add_to_stack_pointer(16);
    }
};

端的に言うとC的なメモリ確保と解放の操作をやってるんですよね。実に原始的です。これ、javascriptですよ。書く気起きます?(笑) 起きませんね!こういうことを考えると、Rustは良い自動化ツールが揃っているようです。

wasm-packとwasm-bindgenでNode・ブラウザ連携が圧倒的に楽になる

RustがWebAssemblyに強い、ということを間違いなく感じたのがこのツールです。

wasm-packはビルドラッパーみたいなツールで、ブラウザ・Node.jsに対応した、いわゆるnode_modulesに入れるパッケージを作ってくれるツールです。

cargo install wasm-pack

wasm-bindgenCargo.tomlで導入。

[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"

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

[dependencies]
wasm-bindgen = "0.2.92"

使う時はこんな感じ

wasm-pack build --target nodejs
# Nodeプロジェクトへの紐付け(node_modulesにインストール)
npm link ./pkg

たったこれだけで超〜簡単にRustでの処理をwasm化し、更にはjavascriptパッケージ化してくれます。すごい。

Nodeのスクリプトはとてもラクです。以下は適当なjpegファイルを置いておき、wasmで読んでコピーを取得、それを別ファイルに書き込む処理です。

const wasm = require("hello-wasm");
const fs = require("fs");

const PATH = "test.jpg";

const buf = fs.readFileSync(PATH);
const res = wasm.get_buffer(buf);

fs.writeFileSync("output.jpg", res);
console.log("Wrote output.jpg");

素晴らしい簡潔さ。夢が広がりますね。

ちょっと個人的にはWebAssembly結構面白い技術だと思うので、もうちょっと深ぼっていきたいです。