話題の記事

RustからWebAssembly (wasm)を生成してJavaScriptとブリッジ通信してみる

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Rustはじめました

Rustイイですよね。Cと同程度のパフォーマンスで動作して、メモリリークを防止し、マルチスレッドも安全に記述できます。GC(ガベージコレクション)処理よりも高速にメモリーを開放します。今まで、より高速に動くプログラミング言語を求めるとC/C++等が必要になり、安全面で気をつけないところが増えてしまって手が出しづらく、一方で、Javaなどの安全面が確保された言語や、JavaScriptやPythonなどのスクリプトで記述できる言語は、nullやundefinedなどを気をつける必要があり、更に実行速度を上げるには課題がありました。(Javaは十分速いと思います)

Rustは、C/C++と同程度のパフォーマンス(強い)を持ちつつ、型やメモリやスレッドに関する安全面を備えているため、今後、OSやミドルウェアの開発に使われることが増えるのではと思っています。一方で、JavaScriptなどのスクリプト言語の使いやすさはエコシステムもあり、応用範囲が広く、今後も多くの人に使われるのではと思っています。

WebAssembly

WebAssemblyは、ブラウザ内でネイティブアプリに近い速度で動作する、コンパクトなバイナリー形式の低レベルなアセンブリ言語です。JavaScriptの実行時にどうしてもパフォーマンスが出ない時に、JavaScriptからWebAssemblyを呼び出すことで、特定の処理を高速に動作させることができるようになります。

今回は、Rustを使ってWebAssembly形式のコードを生成し、JavaScriptからRust、RustからJavaScript、という双方向の通信を実現してみたいと思います。

マシン環境

  • macOS Big Sur version 11.4
  • MacBook Pro (13インチ, 2020, Thunderbolt 3ポート x 4)
  • プロセッサ 2.3 GHz クアッドコアIntel Core i7
  • メモリ 32 GB 3733 MHz LPDDR4X
  • グラフィックス Intel Iris Plus Graphics 1536 MB

環境構築

Rustをインストールします。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ rustup --version
rustup 1.24.3 (ce5817a94 2021-05-31)

$ cargo --version
cargo 1.53.0 (4369396ce 2021-04-27)

RustからWebAssemblyを生成する便利ツールをインストールします。

wasm-packのインストール
$ cargo install wasm-pack

サンプルプロジェクトのセットアップ

WASM用にライブラリの新規作成します。

$ cargo new --lib hello-wasm
Created library hello-wasm package
$ cd hello-wasm/
$ ls
Cargo.toml src
$ cat Cargo.toml
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2018"
[dependencies]

双方向通信するコード

テスト用のダミーコードは消して新しいコードを書きます。

$ vi src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}

簡単に解説します。wasm_bindgenは、JavaScriptとRustをブリッジするためのライブラリ。preludeは、関連するクラスを良い感じにインポートする宣言。

wasm-packは、内部でwasm_bindgenライブラリを使っていて、#[wasm_bindgen]属性を宣言しているブロックは、ブリッジの対象となる。externは、RustからJavaScriptを呼ぶ宣言。pubは、JavaScriptからRustを呼ぶときに公開メソッドである宣言。

全体の流れとしては、JavaScriptからRustのgreetメソッドが呼ばれた時に、引数で渡される文字列から新しい文字列"Hello 〇〇"にして、RustからJavaScriptのalertメソッドを読んでいます。結果として、JavaScript側でalertメソッドが実行されて、"Hello 〇〇"が表示されます。とても簡単ですね。

コンパイル

RustのコードをコンパイルしてWebAssemblyにします。まずは設定ファイルの記述です。

$ vi Cargo.toml

[package]
name = "hello-wasm"
version = "0.1.0"
license = "Apache-2.0"
edition = "2018"

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

[dependencies]
wasm-bindgen = "0.2"

コンパイルします。WebAssemblyのコード(wasm)が生成されていることを確認します。

$ wasm-pack build --target web
[INFO]: ? Your wasm pkg is ready to publish at /Users/akari/hello-wasm/pkg.

$ ls -la pkg
total 72
drwxr-xr-x 8 akari staff 256 7 10 15:10 .
drwxr-xr-x 9 akari staff 288 7 10 15:12 ..
-rw-r--r-- 1 akari staff 1 7 10 15:10 .gitignore
-rw-r--r-- 1 akari staff 794 7 10 15:10 hello_wasm.d.ts
-rw-r--r-- 1 akari staff 3848 7 10 15:10 hello_wasm.js
-rw-r--r-- 1 akari staff 15798 7 10 15:10 hello_wasm_bg.wasm
-rw-r--r-- 1 akari staff 265 7 10 15:10 hello_wasm_bg.wasm.d.ts
-rw-r--r-- 1 akari staff 249 7 10 15:10 package.json

動作確認

HTMLを作成して、先程作成したWebAssemblyを呼ぶ処理を記述します。

$ vi hello.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="module">
import init, {greet} from "./pkg/hello_wasm.js";
init()
.then(() => {
greet("WebAssembly")
});
</script>
</body>
</html>

作成したHTMLを呼び出すために、pythonでワンライナーの簡易Webサーバーを立てます。

$ python --version
Python 2.7.16
$ python -m SimpleHTTPServer 8080

ブラウザからアクセスすると、ポップアップでアラートが表示されて、無事に動作していることが確認できました。

まとめ

Rustを使って、とても簡単にWebAssemblyのコードを作成することができました。これで、通常の開発はJavaScriptを中心に記述し、高速な処理を必要とする部分をRustで記述してWebAssemblyでブリッジすることで、使いやすさと安全面と速度を手に入れることができそうです。 なお、JavaScriptは素で書くのではなく、TypeScriptとVSCodeなどのIDEと合わせることで、静的な片付けによって中大規模な開発にも使えて、更に生産性を上げることができると思います。

参考資料

Compiling from Rust to WebAssembly