🐈

WebAssemblyに入門してみた

2025/02/16に公開

WebAssembly
ブラウザでPostgreSQLが動く、ターミナルも動く
なんて、素晴らしい

ということで、入門
ついでに、興味のあったRustも使ってみる

Fedoraで検証してます。
動かしてみるが優先なので構成とか解説などはないです。。

環境準備

Rustのインストール

ここは手抜きでasdfを使う
asdfのインストールはGetting Startedの通りなので、省略。。。

Rustをインストールするためには、asdfにpluginをインストール

asdf plugin-list-all | grep -i rust
→ plugin-list-allで全pluginの一覧が出力される
→ めちゃくちゃ多いので、grep する

asdf plugin-add rust
asdf plugin-list
→ rustとでればpluginの追加完了

Rustをインストール

asdf install rust <バージョン>
→ バージョンはタブを押せば補完で表示されるので好きなものを
→ (例) asdf install rust 1.83.0
asdf global rust <バージョン>
→ デフォルトで使うバージョンを設定しておく

Rustでwasmを作る準備

Rust and WebAssemblyのSetupに従って準備。必要なものは以下

  • rustup, rustc, cargo(Rustインストール時に含まれている)
  • wasm-pack
  • cargo-generate
  • npm

wasm-pack, cargo-generateを入れた後は以下コマンドでshim配下を更新しとかないとPATHが通らず面倒なので、忘れずに。こちらの記事に感謝

asdf reshim rust

wasm-pack

4つばかりインストール手段があるが、cargoを選択
gccを事前にインストールしてから以下コマンド実行

cargo install wasm-pack

cargo-generate

これもcargoを選択
openssl-develを事前にインストールしておく。

cargo install cargo-generate

npm

これは、asdfに頼る

asdf plugin-add nodejs
asdf install nodejs <バージョン>
which npm
→ ~/.asdf/shims/npm
npm install npm@latest -g
→ npmをlatestにバージョンアップ

wasm32-unknown-unknown

このままチュートリアル通りwasm-pack buildしてもwasm32-unknown-unknownがないと怒られるのでいれるここに従って、以下コマンドを実行するとsysrootにwasm32-unknown-unknownを入れてくれる

rustup target add wasm32-unknown-unknown

sysrootは rustc --print sysroot コマンドで確認可能

チュートリアル(Hello World)

https://rustwasm.github.io/docs/book/game-of-life/hello-world.html

上記に従って、まずはHello Worldをやってみる。
が、そのままだとエラー吐くので、修正。詳細はこちらに書きました。

チュートリアルコードを変えてみる

その1

とりあえず、練習で固定文字列を表示するだけじゃなくて、1から10までの和を表示してみる。

src/lib.rsファイルのgreet関数を以下のように変えてみて、wasm-pack build を実行

pub fn greet() {
    let mut sum = 0;
    for n in 1..=10 {
        sum += n;
    }
    alert(sum.to_string());
}

エラーになることが分かってて、やってみた。
help: consider borrowing here あたり、非常に便利。
これが言いたくて、あえてエラーケース書きました。

   Compiling wasm-game-of-life v0.1.0 (/tmp/wasm-game-of-life)
error[E0308]: mismatched types
  --> src/lib.rs:16:11
   |
16 |     alert(sum.to_string());
   |     ----- ^^^^^^^^^^^^^^^ expected `&str`, found `String`
   |     |
   |     arguments to this function are incorrect
   |
note: function defined here
  --> src/lib.rs:7:8
   |
7  |     fn alert(s: &str);
   |        ^^^^^ -------
help: consider borrowing here
   |
16 |     alert(&sum.to_string());
   |           +

ということで、正しいコードは以下

pub fn greet() {
    let mut sum = 0;
    for n in 1..=10 {
        sum += n;
    }
    alert(&sum.to_string());
}

greet関数に引数をつけて呼べばテキストボックスの内容とか渡せるんだろうなぁと。

簡単な解説

Rust and WebAssembly 4.2 にも書いてるけど。

ディレクトリ構成

覚えておくのは以下でいいと思う。

wasm-game-of-life
  +-- src  <--- Rustのコードを格納しておく
  +-- pkg  <--- wasm-pack buildコマンドを実行すると作成される。wasmファイル(コンパイルされたファイル)。それを呼び出すjs,tsファイルなどが格納される
  +-- www  <--- フロントエンド周り

実際にRustのコードが呼び出されるまでの流れ

Rustで書いたgreet関数が呼び出されるまでの流れは以下の通り。

index.html
↓
bootstrap.js
↓
index.js
↓
greet関数

呼び出し周りはwasm-bindgenにお任せぐらいでいいかも。
何か必要なことがあれば、それはその時にお勉強で。。。

コード回り

用意されているサンプルコード

lib.rs
mod utils;

use wasm_bindgen::prelude::*;

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

#[wasm_bindgen]
pub fn greet() {
    alert("Hello, wasm-game-of-life!");
}

おまじない

#[wasm_bindgen]

このアトリビュートがwasm-bindgenを使うところを示している。

JavaScriptの関数をRust側で使えるようにする

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

ここは、Rustから呼びたい、JavaScriptの関数を記述。C言語のexternと同じくと考えてもらえば。
上記例は、JavaScriptのalert関数を使うための定義。
externの後ろの"C"はABIを指定。この場合はC固定でいいっぽい。ちなみに指定可能なABIはこちら
CはデフォルトなのでABIの指定は不要でもいいかも。

引数の型を何にすればいいんだろ。どこかに一覧があるのか、一定のルールがあるのか。

Rustの関数をJavaScript側で使えるようにする

#[wasm_bindgen]
pub fn greet() {
    alert("Hello, wasm-game-of-life!");
}

Rust側で通常通り、関数を定義し、その上のおまじないの #[wasm_bindgen] を書いてあげるとJavaScript側で利用可能になる模様。
ここで呼んでるalert関数は、JavaScriptの関数。

総じて

細かいところはwasm-bindgenにお任せできるので、大体の流れが理解できればよさげ。

Discussion