paint-brush
30 秒で Rust タイプを TypeScript for WebAssembly と共有する方法: クイックガイド@dawchihliou
930 測定値
930 測定値

30 秒で Rust タイプを TypeScript for WebAssembly と共有する方法: クイックガイド

Daw-Chih Liou6m2023/05/29
Read on Terminal Reader

長すぎる; 読むには

💡 公式の Rust および WebAssembly ツールチェーンが TypeScript にとって十分ではない理由を学びます。 🤹 Rust コードの変更を最小限に抑えて TypeScript 定義を自動生成する方法を説明します。 🧪 実際の WebAssembly ライブラリを npm 上で一緒にリファクタリングします。
featured image - 30 秒で Rust タイプを TypeScript for WebAssembly と共有する方法: クイックガイド
Daw-Chih Liou HackerNoon profile picture

Rust と WebAssembly を使用した最もシームレスな開発者エクスペリエンスを発見してください。これは、Rust コードから TypeScript 定義を自動生成する最も速い方法です。

記事上で

  • 💡 公式の Rust および WebAssembly ツールチェーンが TypeScript にとって十分ではない理由を学びます。


  • 🤹 Rust コードの変更を最小限に抑えて TypeScript 定義を自動生成する方法を説明します。


  • 🧪 実際の WebAssembly ライブラリを npm 上で一緒にリファクタリングします。


さあ行こう。


wasm-bindgen の入力の問題

Rust で WebAssembly(Wasm) モジュールの TypeScript タイプを生成するのは簡単ではありません。


Wasm でVoyというベクトル類似性検索エンジンを開発していたときに、この問題に遭遇しました。私は、JavaScript および TypeScript エンジニアにセマンティック検索のためのスイス ナイフを提供するために、Rust で Wasm エンジンを構築しました。 Web 用のデモは次のとおりです。


ヴォイのデモ


Voy のリポジトリは GitHub にあります。ぜひお試しください。


リポジトリには、さまざまなフレームワークで Voy を使用する方法を確認できるサンプルが含まれています。


wasm-packwasm-bindgenを使用して、Rust コードをビルドして Wasm にコンパイルしました。生成された TypeScript 定義は次のようになります。


 /* tslint:disable */ /* eslint-disable */ /** * @param {any} input * @returns {string} */ export function index(resource: any): string /** * @param {string} index * @param {any} query * @param {number} k * @returns {any} */ export function search(index: string, query: any, k: number): any


ご覧のとおり、「any」型が多数ありますが、これは開発者のエクスペリエンスにはあまり役に立ちません。何が起こったのかを知るために Rust コードを調べてみましょう。


 type NumberOfResult = usize; type Embeddings = Vec<f32>; type SerializedIndex = String; #[derive(Serialize, Deserialize, Debug)] pub struct EmbeddedResource { id: String, title: String, url: String, embeddings: Embeddings, } #[derive(Serialize, Deserialize, Debug)] pub struct Resource { pub embeddings: Vec<EmbeddedResource>, } #[wasm_bindgen] pub fn index(resource: JsValue) -> SerializedIndex { /* snip */ } #[wasm_bindgen] pub fn search(index: &str, query: JsValue, k: NumberOfResult) -> JsValue { // snip }


文字列、スライス、および符号なし整数は TypeScript で正しい型を生成しましたが、「 wasm_bindgen::JsValue 」は生成しませんでした。 JsValue は、wasm-bindgen による JavaScript オブジェクトの表現です。


JsValueをシリアル化および逆シリアル化して、Wasm を介して JavaScript と Rust の間でやり取りします。


 #[wasm_bindgen] pub fn index(resource: JsValue) -> String { // 💡 Deserialize JsValue in to Resource struct in Rust let resource: Resource = serde_wasm_bindgen:from_value(input).unwrap(); // snip } #[wasm_bindgen] pub fn search(index: &str, query: JsValue, k: usize) -> JsValue { // snip // 💡 Serialize search result into JsValue and pass it to WebAssembly let result = engine::search(&index, &query, k).unwrap(); serde_wasm_bindgen:to_value(&result).unwrap() }


これはデータ型を変換するための公式のアプローチですが、明らかに、TypeScript をサポートするにはさらに努力する必要があります。

Tsify による TypeScript バインディングの自動生成

ある言語から別の言語へのデータ型の変換は、実際には外部関数インターフェイス(FFI) と呼ばれる一般的なパターンです。 Rust 構造体から TypeScript 定義を自動生成するTypeshareなどの FFI ツールを検討しましたが、それは解決策の半分にすぎませんでした。


必要なのは、Wasm コンパイルを利用して、Wasm モジュールの API の型定義を生成する方法です。このような:


 #[wasm_bindgen] pub fn index(resource: Resource) -> SerializedIndex { /* snip */ } #[wasm_bindgen] pub fn search(index: SerializedIndex, query: Embeddings, k: NumberOfResult) -> SearchResult { // snip }


幸いなことに、 Tsify はこのユースケースにとって素晴らしいオープンソース ライブラリです。私たちがする必要があるのは、「Tsify」トレイトから派生し、 #[tsify] マクロを構造体に追加することだけです。


 type NumberOfResult = usize; type Embeddings = Vec<f32>; type SerializedIndex = String; #[derive(Serialize, Deserialize, Debug, Clone, Tsify)] #[tsify(from_wasm_abi)] pub struct EmbeddedResource { pub id: String, pub title: String, pub url: String, pub embeddings: Embeddings, } #[derive(Serialize, Deserialize, Debug, Tsify)] #[tsify(from_wasm_abi)] pub struct Resource { pub embeddings: Vec<EmbeddedResource>, } #[derive(Serialize, Deserialize, Debug, Clone, Tsify)] #[tsify(into_wasm_abi)] pub struct Neighbor { pub id: String, pub title: String, pub url: String, } #[derive(Serialize, Deserialize, Debug, Clone, Tsify)] #[tsify(into_wasm_abi)] pub struct SearchResult { neighbors: Vec<Neighbor>, } #[wasm_bindgen] pub fn index(resource: Resource) -> SerializedIndex { /* snip */ } #[wasm_bindgen] pub fn search(index: SerializedIndex, query: Embeddings, k: NumberOfResult) -> SearchResult { // snip }


それでおしまい!属性「from_wasm_abi」と「into_wasm_abi」を見てみましょう。


ワズムABI


どちらの属性も Rust データ型を TypeScript 定義に変換します。両者の違いは、Wasm の Application Binary Interface (ABI) を使用したデータ フローの方向です。


  • into_wasm_abi : データは Rust から JavaScript に流れます。戻り値の型に使用されます。


  • from_wasm_abi : データは JavaScript から Rust に流れます。パラメータに使用されます。


どちらの属性も serde-wasm-bindgen を使用して、Rust と JavaScript 間のデータ変換を実装します。


Wasm モジュールを構築する準備ができました。 「wasm-pack build」を実行すると、自動生成された TypeScript 定義が次のようになります。


 /* tslint:disable */ /* eslint-disable */ /** * @param {Resource} resource * @returns {string} */ export function index(resource: Resource): string /** * @param {string} index * @param {Float32Array} query * @param {number} k * @returns {SearchResult} */ export function search( index: string, query: Float32Array, k: number ): SearchResult export interface EmbeddedResource { id: string title: string url: string embeddings: number[] } export interface Resource { embeddings: EmbeddedResource[] } export interface Neighbor { id: string title: string url: string } export interface SearchResult { neighbors: Neighbor[] }


すべての「any」型は、Rust コードで定義したインターフェイスに置き換えられます✨

最終的な考え

生成された型は適切に見えますが、いくつかの不一致があります。よく見ると、検索関数のクエリ パラメーターが Float32Array として定義されていることがわかります。


クエリパラメータはEmbeddedResourceの「embeddings」と同じ型として定義されているため、TypeScriptでも同じ型であることが期待されます。


異なるタイプに変換される理由がわかっている場合は、遠慮なく連絡するか、 GitHub の Voyでプル リクエストを開いてください。


Voyは、WebAssembly のオープンソースのセマンティック検索エンジンです。より多くのプロジェクトがセマンティック機能を構築し、世界中の人々により良いユーザー エクスペリエンスを提供できるようにするために作成しました。 Voy はいくつかの設計原則に従っています。


  • 🤏 Tiny : ネットワークが遅いモバイルブラウザや IoT など、限られたデバイスのオーバーヘッドを削減します。


  • 🚀高速: ユーザーに最高の検索エクスペリエンスを提供します。


  • 🌳 Tree Shakable : バンドル サイズを最適化し、Web Workers などの最新の Web API の非同期機能を有効にします。


  • 🔋再開可能: いつでもどこでもポータブルなエンベディングインデックスを生成します。


  • ☁️ワールドワイド: CDN エッジ サーバーでセマンティック検索を実行します。


npmで入手可能です。お気に入りのパッケージ マネージャーを使用してインストールするだけで準備完了です。


 # with npm npm i voy-search # with Yarn yarn add voy-search # with pnpm pnpm add voy-search


ぜひお試しください。ご意見をお待ちしております。

参考文献


接続したいですか?

この記事はもともとDaw-Chih の Web サイトに掲載されたものです。