Lass uns gehen.
Das Generieren von TypeScript-Typen für WebAssembly(Wasm)-Module in Rust ist nicht einfach.
Ich bin auf das Problem gestoßen, als ich in Wasm an einer Vektorähnlichkeitssuchmaschine namens Voy gearbeitet habe. Ich habe die Wasm-Engine in Rust erstellt, um JavaScript- und TypeScript-Ingenieuren ein Schweizer Messer für die semantische Suche zur Verfügung zu stellen. Hier ist eine Demo für das Web:
Sie finden das Voy-Repository auf GitHub ! Probieren Sie es gerne aus.
Das Repository enthält Beispiele , mit denen Sie sehen können, wie Sie Voy in verschiedenen Frameworks verwenden.
Ich habe wasm-pack und wasm-bindgen verwendet, um den Rust-Code zu erstellen und in Wasm zu kompilieren. Die generierten TypeScript-Definitionen sehen folgendermaßen aus:
/* 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
Wie Sie sehen, gibt es viele „any“-Typen, was für die Entwicklererfahrung nicht sehr hilfreich ist. Schauen wir uns den Rust-Code an, um herauszufinden, was passiert ist.
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 }
Der String, das Slice und die vorzeichenlose Ganzzahl generierten die richtigen Typen in TypeScript, „ wasm_bindgen::JsValue “ jedoch nicht. JsValue ist die Darstellung eines JavaScript-Objekts durch wasm-bindgen.
Wir serialisieren und deserialisieren den JsValue, um ihn über Wasm zwischen JavaScript und Rust hin und her zu übergeben.
#[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() }
Dies ist der offizielle Ansatz zum Konvertieren von Datentypen, aber wir müssen natürlich noch einen Schritt weiter gehen, um TypeScript zu unterstützen.
Das Konvertieren von Datentypen von einer Sprache in eine andere ist eigentlich ein gängiges Muster, das als Foreign Function Interface (FFI) bezeichnet wird. Ich habe FFI-Tools wie Typeshare ausprobiert, um TypeScript-Definitionen automatisch aus Rust-Strukturen zu generieren, aber das war nur die halbe Lösung.
Was wir brauchen, ist eine Möglichkeit, auf die Wasm-Kompilierung zuzugreifen und die Typdefinition für die API des Wasm-Moduls zu generieren. So was:
#[wasm_bindgen] pub fn index(resource: Resource) -> SerializedIndex { /* snip */ } #[wasm_bindgen] pub fn search(index: SerializedIndex, query: Embeddings, k: NumberOfResult) -> SearchResult { // snip }
Glücklicherweise ist Tsify eine erstaunliche Open-Source-Bibliothek für diesen Anwendungsfall. Alles, was wir tun müssen, ist, vom Merkmal „Tsify“ abzuleiten und den Strukturen ein #[tsify]-Makro hinzuzufügen:
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 }
Das ist es! Werfen wir einen Blick auf die Attribute „from_wasm_abi“ und „into_wasm_abi“.
Beide Attribute konvertieren den Rust-Datentyp in eine TypeScript-Definition. Was sie anders machen, ist die Richtung des Datenflusses mit Wasms Application Binary Interface (ABI).
Beide Attribute verwenden serde-wasm-bindgen, um die Datenkonvertierung zwischen Rust und JavaScript zu implementieren.
Wir sind bereit, das Wasm-Modul zu erstellen. Sobald Sie „wasm-pack build“ ausführen, wird die automatisch generierte TypeScript-Definition:
/* 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[] }
Alle „any“-Typen werden durch die Schnittstellen ersetzt, die wir im Rust-Code definiert haben✨
Die generierten Typen sehen gut aus, es gibt jedoch einige Inkonsistenzen. Wenn Sie genau hinschauen, werden Sie feststellen, dass der Abfrageparameter in der Suchfunktion als Float32Array definiert ist.
Der Abfrageparameter ist als derselbe Typ wie „embeddings“ in EmbeddedResource definiert, daher erwarte ich, dass sie in TypeScript denselben Typ haben.
Wenn Sie wissen, warum sie in unterschiedliche Typen konvertiert werden, zögern Sie bitte nicht, Voy auf GitHub zu kontaktieren oder eine Pull-Anfrage zu eröffnen.
Voy ist eine semantische Open-Source-Suchmaschine in WebAssembly. Ich habe es erstellt, um mehr Projekten die Möglichkeit zu geben, semantische Funktionen zu entwickeln und bessere Benutzererlebnisse für Menschen auf der ganzen Welt zu schaffen. Voy folgt mehreren Designprinzipien:
Es ist auf npm verfügbar. Sie können es einfach mit Ihrem bevorzugten Paketmanager installieren und schon kann es losgehen.
# with npm npm i voy-search # with Yarn yarn add voy-search # with pnpm pnpm add voy-search
Probieren Sie es aus, ich freue mich von Ihnen zu hören!
Möchten Sie eine Verbindung herstellen?
Dieser Artikel wurde ursprünglich auf der Website von Daw-Chih veröffentlicht.