JavaScriptからRustへ、およびその逆:wasm-bindgenについての物語


WebAssemblyのコンパイルjsライブラリの高速化 、およびよりコンパクトなバイナリの生成の速さはすでに確認しました。 RustとJavaScriptのコミュニティ間だけでなく、他の言語のコミュニティとの相互作用を確立する方法についての一般的な考えさえあります。 前回の記事で 、特別なツールwasm-bindgenについて言及しましたが、ここでさらに詳しく説明したいと思います。


現在、 WebAssembly仕様では、2つの整数型と2つの浮動小数点型の4つのデータ型のみが説明されています。 ただし、ほとんどの場合、JSおよびRustの開発者は、より豊富な型システムを使用します。 たとえば、JS開発者はドキュメントオブジェクトと対話してHTMLノードを追加または変更しますが、Rust開発者はResultなどのタイプでエラー処理を行い、ほとんどすべての開発者は文字列を使用します。


WebAssemblyが定義するタイプのみに限定されるのは不便であり、wasm-bindgenは私たちの助けになります。 wasm-bindgenの主なタスクは、RustとJSタイプのシステム間のブリッジを提供することでした。 JS関数は、通常の文字列を渡すことでRust APIを呼び出したり、JSから例外をキャッチするためにRust関数を呼び出したりできます。 wasm-bindgenは、型の不一致を補正し、JavaScriptからWebAssembly関数を効率的かつ簡単に使用できるようにします。


READMEで wasm-bindgenプロジェクトの詳細な説明を見つけることができます。 最初に、wasm-bindgenの簡単な使用例を見てから、それをどのように使用できるかを見てみましょう。


こんにちは世界!


時代を超越したクラシック。 新しいツールを試す最良の方法の1つは、メッセージ「Hello World」の出力のバリエーションを調べることです。 この場合、それを行うを見てみましょう-「Hello World」というダイアログボックスが表示されます。


ここでの目標は簡単です。名前を取得し、 Hello, ${name}!というダイアログボックスを表示するRust関数を作成しますHello, ${name}! 。 JavaScriptでは、次のように記述します。


 export function greet(name) { alert(`Hello, ${name}!`); } 

ただし、この関数をRustで記述します。 これが機能するためには、次の手順が必要です。



まず、新しいRustプロジェクトを作成します。


 cargo new wasm-greet --lib 

このコマンドは、作業するwasm-greetフォルダーを作成します。 次のステップでは、 Cargo.toml (Rustのpackage.jsonアナログ)に次の情報を追加します。


 [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2" 

とりあえず 、libセクションの内容はスキップし、 dependenciesセクションでは、 wasm-bindgenパッケージへのプロジェクトの依存関係を示します 。 このパッケージには、プロジェクトでwasm-bindgenを使用するために必要なすべてが含まれています。


それでは、コードを追加しましょう! src/lib.rs内容を次のコードに置き換えます。


 #![feature(proc_macro, wasm_custom_section, wasm_import_module)] extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { fn alert(s: &str); } #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } 

Rustを初めて使用する場合、上記の例は少し冗長に見えるかもしれませんが、心配しないでください。 wasm-bindgenプロジェクトは絶えず改善されており、将来、そのような詳細な説明の必要性はなくなると確信しています。 ここで最も重要な部分は、属性#[wasm_bindgen]です。 これはRustの注釈であり、必要に応じてこの関数を別の関数にラップする必要があることを示しています。 両方の関数( alert関数のインポートとgreet関数のエクスポートの両方)にこの属性があります。 少し後、ボンネットの下を見て、そこで何が起こるかを確認します。


しかし、最初に、wasmコードをコンパイルしてブラウザーで開きましょう。


 $ rustup target add wasm32-unknown-unknown --toolchain nightly #     $ cargo +nightly build --target wasm32-unknown-unknown 

完了すると、 target/wasm32-unknown-unknown/debug/wasm_greet.wasm配置されるwasmファイルを取得します。 wasm2watのようなものを使用してこのファイルの中を見ると、その内容は少し威圧的に見えるかもしれません。 wasmファイルはまだJSから使用する準備ができていないことがわかります。 これを行うには、もう1つの手順が必要です。


 $ cargo install wasm-bindgen-cli #     $ wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_greet.wasm --out-dir . 

このステップで、すべての魔法が起こります。 wasm-bindgenコマンドはwasmファイルを処理し、使用できる状態にします。 少し後で「すぐに使用できる」という意味を見て、今作成したwasm_greet.jsモジュールをインポートすると、Rustで宣言されているgreet関数が含まれることになります。


これで、パッカーを使用して、コードを実行するHTMLページを作成できます。 この記事の執筆時点では、 Webpack 4.0のみが、 すぐに使用できる十分なWebAssemblyサポートを備えています(ただし、現時点ではChromeブラウザーに問題があります)。 確かに、時間の経過とともに、ますます多くのパッカーがWebAssemblyのサポートを追加していくでしょう。 詳細は説明しません。 リポジトリでWebPackのサンプル設定を確認できます 。 JSファイルの内容を見ると、次のことがわかります。


 const rust = import("./wasm_greet"); rust.then(m => m.greet("World!")); 

...そしてそれだけです。 ブラウザでページを開くと、「 Hello, World!という碑文が表示されたダイアログボックスが表示されますHello, World! Rustで作成されました。


wasm-bindgenの仕組み


ちょっと大きい、 Hello, World! 。 フードの下で何が起こるか、このツールがどのように機能するかを見てみましょう。


wasm-bindgenの最も重要な側面の1つは、wasmモジュールが単なるESモジュールの一種であるという基本概念に基づいて統合が行われることです。 上記の例では、次の署名(TypeScript)を使用してESモジュールを作成したかっただけです。


 export function greet(s: string); 

WebAssemblyにはこれを実行する機能がないため(現時点ではwasmが数字のみをサポートしていることに注意してください)、wasm-bindgenを使用して空白を埋めます。 最後の例の最後のステップで、 wasm-bindgenを起動すると、 wasm_greet.jsファイルだけでなく、 wasm_greet.jsも作成されwasm_greet_bg.wasm 。 1つ目はJSインターフェースで、これによりRustコードを呼び出すことができます。 また、 *_bg.wasmファイルには、実装とコンパイルされたすべてのコードが含まれています。


./wasm_greetモジュールをインポートすると、JSから呼び出したいRustコードを取得しますが、この段階ではネイティブに実行する方法はありません。 統合プロセスについて説明したので、このコードの実行を見てみましょう。


 const rust = import("./wasm_greet"); rust.then(m => m.greet("World!")); 

ここでは、インターフェイスを非同期的にインポートし、準備が整うまで待機し(wasmモジュールをダウンロードしてコンパイルし)、 greet関数を呼び出します。


非同期読み込みはWebpackの要件ですが、常に可能であるとは限らず、他のパッカーでは異なる方法で実装できることに注意してください。

wasm-bindgenしたwasm_greet.jsファイルの内容を見ると、次のようなものが表示されます。


 import * as wasm from './wasm_greet_bg'; // ... export function greet(arg0) { const [ptr0, len0] = passStringToWasm(arg0); try { const ret = wasm.greet(ptr0, len0); return ret; } finally { wasm.__wbindgen_free(ptr0, len0); } } export function __wbg_f_alert_alert_n(ptr0, len0) { // ... } 

注意してください。 これは最適化されたコードではなく、自動的に生成されたコードであり、必ずしも美しくも小さくもありません。 リンク中の最適化のプロセスでは、Rustでアセンブリを解放し、ミニファイヤを通過した後、はるかに小さくなります。

ここで、wasm-bindgenがどのようにgreet関数を生成したかを確認します。 内部では、モジュールのgreetおよびwasm関数を呼び出しますが、現在は文字列ではなく、引数として渡されたポインタと長さで呼び出されます。 passStringToWasm関数の詳細については、 Lin Clarkの記事を参照してください。 wasm-bindgenを使用していなかった場合、このコードをすべて自分で作成する必要があります。 少し後で__wbg_f_alert_alert_n関数に戻ります。


レベルを下げると、次の興味深いポイント、WebAssemblyのgreet関数が見つかります。 Rustコンパイラが見るコードを見てみましょう。 上記で生成されたJSコードのように、エクスポートされたgreet文字を手書きしなかったことに注意してください。 wasm-bindgenは、個別に必要なすべてを生成しました。つまり、


 pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } #[export_name = "greet"] pub extern fn __wasm_bindgen_generated_greet(arg0_ptr: *mut u8, arg0_len: usize) { let arg0 = unsafe { ::std::slice::from_raw_parts(arg0_ptr as *const u8, arg0_len) } let arg0 = unsafe { ::std::str::from_utf8_unchecked(arg0) }; greet(arg0); } 

ここでは、 greet関数、および#[wasm_bingen] __wasm_bindgen_generated_greetを使用して追加で生成された__wasm_bindgen_generated_greet関数を__wasm_bindgen_generated_greet 。 これはエクスポートされた関数であり( #[export_name]属性とexternキーワードはこれを示します)、ポインターと文字列の長さを取ります。 次に、このペアを&str (Rustの文字列)に変換し、 greet関数に渡します。


言い換えると、wasm-bindgenは2つのラッパーを生成します。1つはJavaScriptでJSからwasmに変換し、もう1つはRustでwasmタイプを取得してRustに変換します。


さて、 alert機能のラッパーの最後のセットを見てみましょう。 Rustのgreet関数は、標準のマクロ形式を使用します! 新しい行を作成し、それをalert機能に渡します。 alert関数を宣言したとき、 #[wasm_bindgen]属性を使用したことを思い出してください。今度は、Rustコンパイラが見るものを見てみましょう。


 fn alert(s: &str) { #[wasm_import_module = "__wbindgen_placeholder__"] extern { fn __wbg_f_alert_alert_n(s_ptr: *const u8, s_len: usize); } unsafe { let s_ptr = s.as_ptr(); let s_len = s.len(); __wbg_f_alert_alert_n(s_ptr, s_len); } } 

これは正確に私たちが書いたものではありませんが、ここで何が起こっているかを明確に見ることができます。 alert関数は、実際には文字列とstrを取り、それをwasmに優しい数字に変換する薄いラッパーです。 その後、 __wbg_f_alert_alert_n関数が__wbg_f_alert_alert_n 、奇妙な部分があります-これは属性#[wasm_import_module]です。
関数をWebAssemblyにインポートするには、それを含むモジュールが必要です。 また、wasm-bindgenはESモジュール上に構築されているため、wasmからのそのような関数のインポートは、ESモジュールからのインポートとして解釈されます。 __wbindgen_placeholder__モジュール__wbindgen_placeholder__実際には存在__wbindgen_placeholder__ん。この期限は、このインポートがwasm-bindgenとJSのラッパーが生成することによって処理されるべきであることを示しています。


そして最後に、パズルの最後のピース(生成されたJSファイル)を取得します。


 export function __wbg_f_alert_alert_n(ptr0, len0) { let arg0 = getStringFromWasm(ptr0, len0); alert(arg0) } 

判明したように、非常に多くのことが内部で発生しており、ブラウザーでJS関数を呼び出すにはかなり長い道のりがありました。 しかし、心配しないでください。wasm-bindgenの重要な側面は、これらすべてが隠されていることです。 あちこちで#[wasm_bindgen]属性を持つRustコードを書くことができます。 そして、JSコードは、あたかも別のJavaScriptモジュールであるかのようにそれを使用できるようになります。


wasm-bindgenで他にできることは何ですか?


wasm-bindgenプロジェクトは非常に野心的で、広い範囲をカバーしており、現時点ではすべてを説明するのに十分な時間がありません。 動作を確認する良い方法は、単純なHello World!からRust のツリーのDOMノードの操作まで、 サンプルをよく理解することです。


一般的に、wasm-bindgenの主な機能は次のとおりです。



追加機能について知りたい場合は、トラッカーをフォローしてください


wasm-bindgenの次は何ですか?


これが最もエキサイティングなトピックの1つであるため、完了する前に、wasm-bindgenプロジェクトの将来について少しお話ししたいと思います。


Rust以外の言語のサポート


wasm-bindgenは、最初の日から、多くの言語で使用できるように設計されました。 現在サポートされている言語はRustのみですが、このツールを使用すると将来的にC / C ++を追加できます。 属性#[wasm_bindgen].wasmファイルに追加のセクションを作成します。このセクションは解析さwasm-bindgenます。 このセクションでは、JSで生成されるバインディングとそのインターフェースについて説明します。 このセクションにはRust固有のものはないため、C / C ++コンパイラを備えたプラグインでも作成できるため、後でwasm-bindgenを使用できるようになります。


私にとって、これは最も刺激的な瞬間です。なぜなら、これがwasm-bindgenようなツールをWebAssemblyとJSを有効にするための標準にすることができると信じているからです。 不要な構成コードなしで実行できる機能が、WebAssemblyにコンパイルできるすべての言語の利点になることを願っています。


JSのバインダーの自動生成


現時点では、 #[wasm_bindgen]を使用してJS関数をインポートする場合の欠点の1つは、すべての関数を自分で記述し、エラーがないことを確認する必要があることです。 時には、このプロセスは非常に退屈で(そしてエラーの原因にもなります)、自動化が必要です。


すべてのWeb APIはWebIDLで指定および記述されており、 WebIDLからすべてのバインダーを自動的に生成することが完全に可能でなければなりません。 つまり、上記の例で行ったようにalert関数を定義する必要はなく、代わりに次のように記述できます。


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

この場合、 webapiパッケージはWebIDL APIの説明から自動的に生成され、エラーが発生しないことが保証されます。


このアイデアをさらに発展させ、TypeScriptコミュニティの印象的な成果を活用し、TypeScript からバインダー生成することもできます。 これにより、TypeScriptをサポートするnpmのパッケージが自動的に使用されます。


JSよりも高速なDOM操作


最後になりましたが、地平線wasm-bindgenでは、超高速DOM操作は多くのJavaScriptフレームワークの聖杯です。 現在、JavaScriptからC ++エンジンに移行する場合、DOMを操作するためのすべての関数呼び出しは、コストのかかる変換を経ています。 WebAssemblyを使用すると、これらの変換はオプションになります。 WebAssembly型システムは...であることを知っています!


最初の日から、 wasm-bindgenコード生成は、ホストへの入札をサポートすることを目的として設計されました。 この関数がWebAssemblyに表示されるとすぐに、wasm-bindgenが生成するラッパーなしでインポートされた関数を直接使用する機会があります。 さらに、これによりJSエンジンはWebAssemblyからのDOM操作を積極的に最適化できます。これは、すべてのインターフェイスが厳密に型指定され、それらを検証する必要がなくなるためです。 この場合、wasm-bindgenは、さまざまなデータ型での作業を容易にするだけでなく、DOMでの作業時にその種の最高のパフォーマンスを提供します。


まとめると


WebAssemblyでの作業は、コミュニティのおかげだけでなく、開発の速さからも非常に興味深いと感じています。 wasm-bindgenプロジェクトには明るい未来があります。 JSとRustの間の簡単な相互運用性を提供するだけでなく、WebAssemblyが進化するにつれて長期的には新しい可能性を開きます。


wasm-bindgenを試して、新しい関数のリクエストを作成し、RustとWebAssembly に連絡してください。



Source: https://habr.com/ru/post/J353230/


All Articles