paint-brush
So teilen Sie Rust-Typen mit TypeScript für WebAssembly in 30 Sekunden: Eine Kurzanleitungvon@dawchihliou
930 Lesungen
930 Lesungen

So teilen Sie Rust-Typen mit TypeScript für WebAssembly in 30 Sekunden: Eine Kurzanleitung

von Daw-Chih Liou6m2023/05/29
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

💡 Wir erfahren, warum die offizielle Rust- und WebAssembly-Toolchain für TypeScript nicht ausreicht. 🤹 Ich zeige Ihnen, wie Sie mit minimalen Änderungen in Ihrem Rust-Code automatisch eine TypeScript-Definition generieren. 🧪 Wir werden gemeinsam eine reale WebAssembly-Bibliothek auf npm umgestalten.
featured image - So teilen Sie Rust-Typen mit TypeScript für WebAssembly in 30 Sekunden: Eine Kurzanleitung
Daw-Chih Liou HackerNoon profile picture

Entdecken Sie das nahtlosste Entwicklererlebnis mit Rust und WebAssembly. Dies ist der schnellste Weg, TypeScript-Definitionen automatisch aus Ihrem Rust-Code zu generieren.

In diesem Artikel

  • 💡 Wir erfahren, warum die offizielle Rust- und WebAssembly-Toolchain für TypeScript nicht ausreicht.


  • 🤹 Ich zeige Ihnen, wie Sie die TypeScript-Definition mit minimalen Änderungen in Ihrem Rust-Code automatisch generieren.


  • 🧪 Wir werden gemeinsam eine reale WebAssembly-Bibliothek auf npm umgestalten.


Lass uns gehen.


Das Tippproblem mit wasm-bindgen

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:


Voy-Demo


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.

Automatisches Generieren einer TypeScript-Bindung mit Tsify

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“.


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).


  • into_wasm_abi : Die Daten fließen von Rust nach JavaScript. Wird für den Rückgabetyp verwendet.


  • from_wasm_abi : Die Daten fließen von JavaScript nach Rust. Wird für Parameter verwendet.


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✨

Abschließende Gedanken

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:


  • 🤏 Winzig : Reduzieren Sie den Overhead für begrenzte Geräte, z. B. mobile Browser mit langsamen Netzwerken oder IoT.


  • 🚀 Schnell : Schaffen Sie das beste Sucherlebnis für die Benutzer.


  • 🌳 Tree Shakable : Optimieren Sie die Bundle-Größe und aktivieren Sie asynchrone Funktionen für moderne Web-APIs, wie z. B. Web Worker.


  • 🔋 Fortsetzbar : Erstellen Sie überall und jederzeit einen tragbaren Einbettungsindex.


  • ☁️ Weltweit : Führen Sie eine semantische Suche auf CDN-Edgeservern durch.


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!

Verweise


Möchten Sie eine Verbindung herstellen?

Dieser Artikel wurde ursprünglich auf der Website von Daw-Chih veröffentlicht.