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

blog1.mammb.com


Rust ライブラリは、動的リンクライブラリとしてビルドすることで、WebAssembly ライブラリを作成できます。 作成した WebAssembly ライブラリは、他の言語などからロードしてライブラリを利用できるようになります。


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 を以下のように編集します。

#[no_mangle]
pub extern "C" fn plus_one(x: i32) -> i32 {
    x + 1
}

Rustコンパイラは、シンボル名をネイティブコードリンカが期待するものとは異なるものにマングルします。 そのため、Rustの外にエクスポートするRustの関数は、マングルしないように#[no_mangle] を指定する必要があります。

また、Rustで書いたいずれの関数も、デフォルトで Rust の ABI(Application Binary Interface)を使います。 代わりに、外に公開する FFI(Foreign Function Interface) API にシステム ABI を使うように、extern "C" を指示する必要があります。


ここでは、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>
   fetch('target/wasm32-unknown-unknown/debug/wasm_js.wasm')
     .then(response => response.arrayBuffer())
     .then(buffer => WebAssembly.instantiate(buffer, {}))
     .then(({module, instance}) => {
       const { plus_one } = instance.exports
       document.body.textContent = `plus_one(1): ${plus_one(1)}`;
     });
    </script>
  </head>
  <body></body>
</html>

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

$ npx serve  # npm

$ jwebserver # java18以降

$ python3 -m http.server 8000 # python

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

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

WebAssembly.instantiateStreaming(
    fetch('target/wasm32-unknown-unknown/debug/wasm_js.wasm'), {})
  .then(({module, instance}) => {
    const { plus_one } = instance.exports
    document.body.textContent = `plus_one(1): ${plus_one(1)}`;
  });


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

例えば文字列をやり取りする場合は、モジュールの線形メモリ内の場所への参照と、文字列の長さを返す必要があります。 ホストしている JavaScriptから文字列を読み出すには、通常は wasm-bindgen などを使い、ブリッジを自動生成させて使うことになります。