Rust で作成した WebAssembly ライブラリから JavaScript 関数を呼び出すには

blog1.mammb.com


Rust ライブラリは、動的リンクライブラリとしてビルドすることで、WebAssembly ライブラリを作成できます。 ここでは、WebAssembly ライブラリから、JavaScript 関数の呼び出し方法を説明します。


cargo でライブラリプロジェクトを作成します。

$ cargo new wasm_js --lib
$ cd wasm_js

Cargo.toml に以下を追加します。

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

Rustでは、WebAssembly に限らず、crate_typecdylib を指定することで、他の言語から呼び出すことのできる動的リンクライブラリをビルドすることができます(通常のライブラリであれば、Linux の場合 *.so、 macOS の場合 *.dylib、Windowsの場合*.dll ファイルが作成されることになります)。


lib.rs を以下のように編集します。

#[link(wasm_import_module = "env")]
extern { fn hello(); }

#[no_mangle]
pub extern "C" fn say() {
    unsafe { hello();}
}

WebAssemblyのモジュールは一連のimportを宣言し、それぞれモジュール名とimport名を持っています。 #[link(wasm_import_module)] でモジュール名を定義し、JavaScript から提供される hello() 関数を宣言しています。

JavaScript で提供する hello() 関数を、JavaScript からキックするために say() を外部に公開します。


ここでは、WebAssembly ライブラリをブラウザから呼び出すので、WASMのビルドターゲットを以下のように指定してビルドします。

$ cargo build --target wasm32-unknown-unknown

もしビルドターゲットが未導入の場合には以下で追加することができます。

$ rustup target add wasm32-unknown-unknown

ビルドが成功すれば target/wasm32-unknown-unknown/debug/wasm_js.wasm が作成されます。


cargo プロジェクトのルートに index.html を以下のように作成します。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello Web Assembly</title>
    <script>
    const importObj = {
      env: {
        hello: () => document.body.textContent = `hello`
      }
    }
   fetch('target/wasm32-unknown-unknown/debug/wasm_js.wasm')
     .then(response => response.arrayBuffer())
     .then(buffer => WebAssembly.instantiate(buffer, importObj))
     .then(({module, instance}) => instance.exports.say())
    </script>
  </head>
  <body></body>
</html>

importObj で定義した関数を WebAssembly.instantiate() の引数として与えます。

Httpサーバを起動します。例えば以下の何れかなどになるでしょう。

$ npx serve  # npm

$ jwebserver # java18以降

$ python3 -m http.server 8000 # python

以下の出力を得ることができます。

ブラウザが instantiateStreaming をサポートしていれば、以下のように書くこともできます。

const importObj = {
  env: {
    hello: () => document.body.textContent = `hello2`
  }
}

WebAssembly.instantiateStreaming(
    fetch('target/wasm32-unknown-unknown/debug/wasm_js.wasm'), importObj)
  .then(({module, instance}) => instance.exports.say())


WebAssemblyと外部とのやり取りには、整数や浮動小数点数などのプリミティブ データ型しか扱うことができません。 WebAssembly はいくつかの数値型のみをサポートし、これがエクスポートされた関数を介して返すことができる全てです。

例えば文字列をやり取りする場合は、共有されたメモリ内の場所への参照と、文字列の長さをやり取りする必要があります。 そのため、通常は wasm-bindgen などを使い、ブリッジを自動生成させて使うことになります。