wasm-bindgen
wasm-bindgen
は、Wasm モジュールと JavaScript の間の高レベルなインタラクションを容易にするためのライブラリおよびツールです。これは、The Rust and WebAssembly Working Group によって Rust で構築されています。
Yew は、いくつかのクレートを通じてブラウザとインタラクトするために wasm-bindgen
を使用します。
このセクションでは、これらのクレートの一部を大まかに探求し、Yew で wasm-bindgen
API を理解しやすく、使いやすくします。 wasm-bindgen
とそれに関連するクレートの詳細なガイドについては、The wasm-bindgen
Guide を参照してください。
上記のクレートのドキュメントについては、wasm-bindgen docs.rs
を参照してください。
wasm-bindgen
の doc.rs 検索を使用して、wasm-bindgen
を使用してインポートされたブラウザ API と JavaScript 型を見つけてください。
wasm-bindgen
このクレートは、上記の他のクレートの残りの部分の多くの構成要素を提供します。このセクションでは、wasm-bindgen
クレートの 2 つの主要な領域、つまりマクロと、何度も表示される一部の型/トレイトのみを取り上げます。
#[wasm_bindgen]
マクロ
#[wasm_bindgen]
マクロは、Rust と JavaScript の間にインターフェースを提供し、2 つの間で翻訳を行うシステムを提供します。このマクロを使用することはより高度であり、外部 JavaScript ライブラリを使用しようとする場合を除き、手を伸ばす必要はありません。 js-sys
および web-sys
クレートは、組み込みの JavaScript 型およびブラウザ API の wasm-bindgen
定義を公開します。
#[wasm_bindgen]
マクロを使用して、console.log
関数のいくつかの特定のフレーバーをインポートする簡単な例を見てみましょう。
use wasm_bindgen::prelude::*;
// First up let's take a look of binding `console.log` manually, without the
// help of `web_sys`. Here we're writing the `#[wasm_bindgen]` annotations
// manually ourselves, and the correctness of our program relies on the
// correctness of these annotations!
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
// The `console.log` is quite polymorphic, so we can bind it with multiple
// signatures. Note that we need to use `js_name` to ensure we always call
// `log` in JS.
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_u32(a: u32);
// Multiple arguments too!
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}
// using the imported functions!
log("Hello from Rust!");
log_u32(42);
log_many("Logging", "many values!");
この例は、The wasm-bindgen
Guide の 1.2 Using console.log を参考にしました.
継承のシミュレーション
JavaScript クラス間の継承は、JavaScript 言語のコア機能であり、DOM (Document Object Model) はそれに基づいて設計されています。 wasm-bindgen
を使用して型がインポートされる場合、継承を記述する属性を追加することもできます。
Rust では、この継承は Deref
および AsRef
トレイトを使用して表現されます。例を挙げると、3 つの型 A
、B
、C
があり、C
が B
を拡張し、次に B
が A
を拡張するとします。
これらの型をインポートする場合、#[wasm_bindgen]
マクロは次の方法で Deref
および AsRef
トレイトを実装します
C
はDeref
をB
に指定できますB
はDeref
をA
に指定できますC
はAsRef
をB
に指定できますC
およびB
の両方をAsRef
をA
に指定できます
これらの実装により、C
のインスタンスで A
のメソッドを呼び出し、C
を &B
または &A
であるかのように使用できます。
#[wasm-bindgen]
を使用してインポートされたすべての単一の型は、同じルート型を持っていることに注意することが重要です。上記の例では A
と考えることができます。この型は JsValue
であり、以下のセクションがあります。
The wasm-bindgen
Guide の extends セクション
JsValue
これは JavaScript が所有するオブジェクトの表現であり、wasm-bindgen
のルートのキャッチオール型です。 wasm-bindgen
から来た型はすべて JsValue
です。これは、JavaScript には強力な型システムがないため、変数 x
を受け入れるすべての関数は、その型を定義しないため、x
は有効な JavaScript 値になる可能性があるため、JsValue
です。 JsValue
を受け入れるインポートされた関数または型を操作している場合、インポートされた値はすべて*技術的に*有効です。
JsValue
は関数で受け入れることができますが、その関数は特定の型のみを受け入れる可能性があり、これによりパニックが発生する可能性があります。そのため、生の wasm-bindgen
API を使用する場合は、その値が特定の型ではない場合に例外 (パニック) が発生するかどうかについて、インポートされている JavaScript のドキュメントを確認してください。
JsCast
Rust には強力な型システムがあり、JavaScript には...ありません 😞。Rust がこれらの強力な型を維持しながらも便利であるために、WebAssembly グループは非常に優れたトレイト JsCast
を考案しました。その役割は、ある JavaScript 「型」から別の型に移行するのを助けることです。これは曖昧に聞こえるかもしれませんが、ある型が別の型であることを知っている場合、JsCast
の関数を使用してある型から別の型にジャンプできることを意味します。 web-sys
、wasm_bindgen
、js-sys
を操作するときに知っておくと便利なトレイトです。これらのクレートから多くの型が JsCast
を実装していることに気づくでしょう。
JsCast
は、チェックされたキャストとチェックされていないキャストの両方のメソッドを提供します。そのため、実行時に、特定のオブジェクトがどの型であるかわからない場合は、キャストを試すことができます。キャストは Option
や Result
のような失敗の可能性のある型を返します。
web-sys
でのこの一般的な例は、イベントのターゲットを取得しようとしている場合です。ターゲット要素が何であるかを知っているかもしれませんが、web_sys::Event
API は常に Option<web_sys::EventTarget>
を返します。メソッドを呼び出せるように、要素の型にキャストする必要があります。
// need to import the trait.
use wasm_bindgen::JsCast;
use web_sys::{Event, EventTarget, HtmlInputElement, HtmlSelectElement};
fn handle_event(event: Event) {
let target: EventTarget = event
.target()
.expect("I'm sure this event has a target!");
// maybe the target is a select element?
if let Some(select_element) = target.dyn_ref::<HtmlSelectElement>() {
// do something amazing here
return;
}
// if it wasn't a select element then I KNOW it's a input element!
let input_element: HtmlInputElement = target.unchecked_into();
}
dyn_ref
メソッドは、チェック済みのキャストで、Option<&T>
を返します。これは、キャストが失敗して None
を返した場合に、元の型を再度使用できることを意味します。 dyn_into
メソッドは、Rust の into
メソッドの規則に従って self
を消費し、返される型は Result<T, Self>
です。キャストが失敗すると、元の Self
値が Err
で返されます。元の型で再度試行するか、他の処理を実行できます。
Closure
Closure
型は、Rust クロージャを JavaScript に転送する方法を提供します。JavaScript に渡されるクロージャは、健全性の理由から 'static
ライフタイムを持つ必要があります。
この型は、破棄されるたびに参照する JS クロージャを無効にするという意味で「ハンドル」です。 Closure がドロップされた後に JS でクロージャを使用すると、例外が発生します。
Closure
は、&js_sys::Function
型を受け入れる js-sys
または web-sys
API を操作する際に頻繁に使用されます。Yew での Closure
の使用例は、イベントページのClosure
を使用するセクションにあります。
js-sys
js-sys
クレートは、JavaScript の標準の組み込みオブジェクト(それらのメソッドとプロパティを含む)のバインディング/インポートを提供します。
これは、web-sys
が担当するため、Web API は含まれません!
wasm-bindgen-futures
wasm-bindgen-futures
クレートは、JavaScript の Promise 型を Rust の Future
として操作するためのブリッジを提供し、Rust の Future を JavaScript の Promise に変換するユーティリティが含まれています。これは、Rust (wasm) で非同期処理やブロッキング処理を行う際に役立ち、JavaScript のイベントや JavaScript の I/O プリミティブとの相互運用性を提供します。
このクレートには現在、3 つの主要なインターフェースがあります。
-
JsFuture
-Promise
で構築される型であり、Future<Output=Result<JsValue, JsValue>>
として使用できます。このFuture
は、Promise
が解決された場合はOk
に、Promise
が拒否された場合はErr
に解決され、それぞれPromise
から解決または拒否された値が含まれます。 -
future_to_promise
- Rust のFuture<Output=Result<JsValue, JsValue>>
を JavaScript のPromise
に変換します。Future の結果は、JavaScript で解決済みまたは拒否済みのPromise
のいずれかに変換されます。 -
spawn_local
- 現在のスレッドでFuture<Output = ()>
を生成します。これは、JavaScript に送信せずに Rust で Future を実行する最良の方法です。
spawn_local
spawn_local
は、非同期 API を持つライブラリを使用する際に役立つため、Yew において wasm-bindgen-futures
クレートで最もよく使用される部分になるでしょう。
use web_sys::console;
use wasm_bindgen_futures::spawn_local;
async fn my_async_fn() -> String { String::from("Hello") }
spawn_local(async {
let mut string = my_async_fn().await;
string.push_str(", world!");
// console log "Hello, world!"
console::log_1(&string.into());
});
Yew は特定の API での Future のサポートも追加しており、特に async
ブロックを受け入れる callback_future
を作成できます。これは内部的に spawn_local
を使用します。