wasm_bindgenの利用方法をコードと共に解説!
以下では、wasm_bindgenについて、より詳しく説明していきます。
wasm_bindgenに指定できる属性
wasm_bindgenは属性を指定することでさまざまな形式のデータ交換を実現できます。この方法を、図2のサンプルで説明します。名前を入力してボタンをクリックすると挨拶文が表示されます。「こんにちは」の挨拶文はconsole.logにも出力されます。
「こんにちは」のJavaScript実装はリスト3の通りです。(1)でテキストボックスから名前の文字列を取得して、それを(2)でRust/WebAssemblyに渡して処理を実行し、挨拶文を取得します。取得した挨拶文は(3)で画面に表示します。
// inputから名前の文字列を取得 ...(1) const name = document.getElementById('name-input').value; // Rust/WebAssemblyで実装したメソッドで挨拶文を取得 ...(2) const greet = wasm.get_greet_text(name); // 挨拶文を画面に表示 ...(3) document.getElementById('greet-text').textContent = greet;
リスト3に対応するRust/WebAssembly側の実装はリスト4の通りです。
// console.logを参照 ...(1) #[wasm_bindgen] extern { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } // 挨拶文を作成して返却 ...(2) #[wasm_bindgen] pub fn get_greet_text(name: &str) -> String { let greet_text = format!("こんにちは、{}さん!", name); log(&greet_text); // console.logにも出力 return greet_text; }
(1)はJavaScriptのconsole.logメソッドをRust/WebAssemblyから実行する記述です。js_namespace属性で、メソッドが含まれる名前空間(ここではconsole)を指定できます。(2)は、JavaScriptから呼ばれるget_greet_textメソッドの実装で、挨拶文を作って、logメソッドでconsole.logへ出力しつつ返却します。なおlogメソッドの引数に「&greet_text」と&がついているのは、文字列をStringから&strに変換する指定です。
一方、「こんばんは」のJavaScript実装はリスト5の通りです。(1)はリスト3と同じですが、取得した挨拶文を(2)のwasm.apply_greet_textメソッドに渡して実行します。
// inputから名前の文字列を取得 ...(1) const name = document.getElementById('name-input').value; // Rust/WebAssemblyで実装した挨拶文表示処理を実行 ...(2) wasm.apply_greet_text(name);
リスト5に対応するRust/WebAssembly側の実装はリスト6の通りです。
// exports.jsからapplyGreetTextメソッドを参照 ...(1) #[wasm_bindgen(module = "/www/exports.js")] extern { fn applyGreetText(s: &str); } // 挨拶文を作成してJavaScriptのapplyGreetTextに渡す ...(2) #[wasm_bindgen] pub fn apply_greet_text(name: &str) { let greet_text = format!("こんばんは、{}さん!", name); applyGreetText(&greet_text); }
(1)は/www/exports.jsに含まれるapplyGreetTextメソッド(文言を画面に表示する処理)をRust/WebAssemblyから実行する記述です。wasm_bindgenのmodule属性で、メソッドが含まれるモジュールを指定できます。(2)では、作成した挨拶文を(1)のapplyGreetTextメソッドに渡して、画面に表示します。
wasm_bindgenに指定できる全ての属性については、公式ドキュメントも参考にしてください。
Rustの構造体をJavaScriptのオブジェクトとして渡す
wasm_bindgenでは、Rustで実装した構造体を、JavaScriptのオブジェクトとして参照することもできます。図3のサンプルでは、内部に整数の数値を持ったカウンターのオブジェクトをRust/WebAseemblyで実装し、それをJavaScriptから操作します。
Rustで実装したカウンターの構造体(RustStructCounter)は、リスト7の通りです。
// Rustの構造体をJavaScriptにエクスポート ...(1) #[wasm_bindgen] pub struct RustStructCounter { counter: u32 } // Rust構造体のメソッドをJavaScriptにエクスポート ...(2) #[wasm_bindgen] impl RustStructCounter { // 構造体を生成 ...(3) pub fn new(init_value: u32) -> RustStructCounter { // counterの初期値を設定しつつ、RustStructCounterを生成 return RustStructCounter { counter: init_value } } // カウンターをリセット ...(4) pub fn reset_value(&mut self) { self.counter = 0; } // カウンターを1増やす ...(5) pub fn add_value(&mut self) { self.counter += 1; } // 現在のカウンター値を取得 ...(6) pub fn get_value(&mut self) -> u32 { return self.counter; } }
(1)で、u32(符号なし32ビット整数)型のcounterを持つRustStructCounter構造体を定義します。Rustの構造体には、(2)のimplキーワードによりメソッドを実装できます。(3)は構造体を生成する処理で、引数で受け取ったinit_valueで内部のcounterを初期化しつつ、構造体を返却します。(4)(5)(6)はカウンター値の操作・取得を行う処理です。メソッドの第1引数に渡される構造体への参照を利用して処理を行います。
対応するJavaScript側の処理はリスト8です。
// RustStructCounterをインポート ...(1) import { RustStructCounter } from 'p003-bindgen-struct'; // カウンター値を表示する処理 ...(2) function applyCounterValue() { document.getElementById('counter-value').textContent = counter.get_value(); } // RustStructCounterを生成 ...(3) const counter = RustStructCounter.new(5); // 生成(初期値:5) applyCounterValue(); // 画面に反映 // 追加ボタンの処理 ...(4) document.getElementById('add-button').addEventListener('click', () => { counter.add_value(); // カウンターを追加 applyCounterValue(); // 画面に反映 }, false); // リセットボタンの処理 ...(5) document.getElementById('reset-button').addEventListener('click', () => { counter.reset_value(); // カウンターをリセット applyCounterValue(); // 画面に反映 }, false);
(1)で、リスト7の実装からRustStructCounterをJavaScriptにインポートします。(2)はカウンター内容を画面に表示する処理で、RustStructCounterから生成したオブジェクトcounterのget_valueメソッドでカウンターの値を取得して画面に表示します。
(3)で、RustStructCounter.newメソッドを利用して、RustStructCounter構造体をJavaScriptオブジェクトcounterとして、初期値5を与えつつ生成します。追加ボタンの処理(4)ではcounter.add_valueメソッドを実行してカウンターを増やし、リセットボタンの処理(5)ではcounter.reset_valueメソッドを実行してカウンターを0に戻しています。