Rust と WebAssembly を使用した最もシームレスな開発者エクスペリエンスを発見してください。これは、Rust コードから TypeScript 定義を自動生成する最も速い方法です。 記事上で 💡 公式の Rust および WebAssembly ツールチェーンが TypeScript にとって十分ではない理由を学びます。 🤹 Rust コードの変更を最小限に抑えて TypeScript 定義を自動生成する方法を説明します。 🧪 実際の WebAssembly ライブラリを npm 上で一緒にリファクタリングします。 さあ行こう。 wasm-bindgen の入力の問題 Rust で WebAssembly(Wasm) モジュールの TypeScript タイプを生成するのは簡単ではありません。 Wasm で というベクトル類似性検索エンジンを開発していたときに、この問題に遭遇しました。私は、JavaScript および TypeScript エンジニアにセマンティック検索のためのスイス ナイフを提供するために、Rust で Wasm エンジンを構築しました。 Web 用のデモは次のとおりです。 Voy あります。ぜひお試しください。 Voy のリポジトリは GitHub に リポジトリには、さまざまなフレームワークで Voy を使用する方法を確認できる 含まれています。 サンプルが と を使用して、Rust コードをビルドして Wasm にコンパイルしました。生成された TypeScript 定義は次のようになります。 wasm-pack wasm-bindgen /* 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 で正しい型を生成しましたが、「 」は生成しませんでした。 JsValue は、wasm-bindgen による JavaScript オブジェクトの表現です。 wasm_bindgen::JsValue 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 定義を自動生成する などの FFI ツールを検討しましたが、それは解決策の半分にすぎませんでした。 外部関数インターフェイス Typeshare 必要なのは、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」を見てみましょう。 どちらの属性も Rust データ型を TypeScript 定義に変換します。両者の違いは、Wasm の Application Binary Interface (ABI) を使用したデータ フローの方向です。 : データは Rust から JavaScript に流れます。戻り値の型に使用されます。 into_wasm_abi : データは JavaScript から Rust に流れます。パラメータに使用されます。 from_wasm_abi どちらの属性も 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 は、WebAssembly のオープンソースのセマンティック検索エンジンです。より多くのプロジェクトがセマンティック機能を構築し、世界中の人々により良いユーザー エクスペリエンスを提供できるようにするために作成しました。 Voy はいくつかの設計原則に従っています。 Voy 🤏 : ネットワークが遅いモバイルブラウザや IoT など、限られたデバイスのオーバーヘッドを削減します。 Tiny 🚀 : ユーザーに最高の検索エクスペリエンスを提供します。 高速 🌳 : バンドル サイズを最適化し、Web Workers などの最新の Web API の非同期機能を有効にします。 Tree Shakable 🔋 : いつでもどこでもポータブルなエンベディングインデックスを生成します。 再開可能 ☁️ : CDN エッジ サーバーでセマンティック検索を実行します。 ワールドワイド npmで入手可能です。お気に入りのパッケージ マネージャーを使用してインストールするだけで準備完了です。 # with npm npm i voy-search # with Yarn yarn add voy-search # with pnpm pnpm add voy-search ぜひお試しください。ご意見をお待ちしております。 参考文献 - ウィキペディア 外部関数インターフェース - GitHub serde-wasm-bindgen - GitHub スペクタ - wasm-bindgen 構造体 wasm_bindgen::JsValue - Rust と WebAssembly Rust と WebAssembly の本 - GitHub ts-rs - GitHub Tsify - GitHub タイプシェア - GitHub ヴォイ - Rust と WebAssembly wasm-bindgen - Rust と WebAssembly wasm-pack - Daw-Chih Liou Rust での WASM セマンティック検索 - WebAssembly.org WebAssembly 接続したいですか? この記事はもともと に掲載されたものです。 Daw-Chih の Web サイト