Rust ve WebAssembly ile en kusursuz geliştirici deneyimini keşfedin. Bu, Rust kodunuzdan TypeScript tanımlarını otomatik olarak oluşturmanın en hızlı yoludur.
Bu makalede
- 💡 Resmi Rust ve WebAssembly araç zincirinin TypeScript için neden yeterli olmadığını öğreneceğiz.
- 🤹 Rust kodunuzda minimum değişiklikle TypeScript tanımını nasıl otomatik olarak oluşturacağınızı göstereceğim.
- 🧪 Gerçek dünyadaki bir WebAssembly kütüphanesini npm'de birlikte yeniden düzenleyeceğiz.
Hadi gidelim.
Wasm-bindgen ile Yazma Sorunu
Rust'ta WebAssembly(Wasm) modülleri için TypeScript türleri oluşturmak kolay değildir.
Wasm'da Voy adında bir vektör benzerlik arama motoru üzerinde çalışırken bu sorunla karşılaştım. Wasm motorunu, JavaScript ve TypeScript mühendislerine anlamsal arama için İsviçre bıçağı sağlamak üzere Rust'ta geliştirdim. İşte web için bir demo:
Voy'un deposunu GitHub'da bulabilirsiniz! Denemekten çekinmeyin.
Depoda Voy'un farklı çerçevelerde nasıl kullanılacağını görebileceğiniz örnekler yer alıyor.
Rust kodunu oluşturup Wasm'a derlemek için wasm-pack ve wasm-bindgen'i kullandım. Oluşturulan TypeScript tanımları şöyle görünür:
/* 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
Gördüğünüz gibi çok sayıda "herhangi" türü var ve bu, geliştirici deneyimi için pek yararlı değil. Ne olduğunu öğrenmek için Rust koduna bakalım.
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 }
Dize, dilim ve işaretsiz tamsayı TypeScript'te doğru türleri oluşturdu ancak " wasm_bindgen::JsValue " oluşturmadı. JsValue wasm-bindgen'in bir JavaScript nesnesini temsilidir.
JsValue'yu, Wasm aracılığıyla JavaScript ve Rust arasında ileri geri aktarmak için seri hale getiriyoruz ve seri durumdan çıkarıyoruz .
#[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() }
Veri türlerini dönüştürmek resmi yaklaşımdır, ancak açıkça TypeScript'i desteklemek için ekstra yol kat etmemiz gerekiyor.
Tsify ile TypeScript Bağlamayı Otomatik Oluştur
Veri türlerini bir dilden diğerine dönüştürmek aslında Yabancı işlev arayüzü (FFI) adı verilen yaygın bir kalıptır. Rust yapılarından TypeScript tanımlarını otomatik olarak oluşturmak için Typeshare gibi FFI araçlarını araştırdım, ancak bu, çözümün yalnızca yarısıydı.
İhtiyacımız olan şey, Wasm derlemesinden yararlanmanın ve Wasm modülünün API'si için tür tanımını oluşturmanın bir yoludur. Bunun gibi:
#[wasm_bindgen] pub fn index(resource: Resource) -> SerializedIndex { /* snip */ } #[wasm_bindgen] pub fn search(index: SerializedIndex, query: Embeddings, k: NumberOfResult) -> SearchResult { // snip }
Neyse ki Tsify , kullanım durumu için harika bir açık kaynaklı kütüphanedir. Tek yapmamız gereken "Tsify" özelliğinden türetmek ve yapılara bir #[tsify] makrosu eklemek:
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 }
Bu kadar! "from_wasm_abi" ve "into_wasm_abi" niteliklerine bir göz atalım.
Her iki özellik de Rust veri türünü TypeScript tanımına dönüştürür. Farklı yaptıkları şey, Wasm'ın Uygulama İkili Arayüzü (ABI) ile veri akışının yönüdür.
- into_wasm_abi : Veriler Rust'tan JavaScript'e akar. Dönüş türü için kullanılır.
- from_wasm_abi : Veriler JavaScript'ten Rust'a akar. Parametreler için kullanılır.
Her iki özellik de Rust ve JavaScript arasındaki veri dönüşümünü uygulamak için serde-wasm-bindgen'i kullanır.
Wasm modülünü oluşturmaya hazırız. "Wasm-pack build"i çalıştırdığınızda, otomatik olarak oluşturulan TypeScript tanımı:
/* 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[] }
Tüm "herhangi" türler, Rust kodunda tanımladığımız arayüzlerle değiştirilir✨
Son düşünceler
Oluşturulan türler iyi görünüyor ancak bazı tutarsızlıklar var. Yakından bakarsanız, arama fonksiyonundaki sorgu parametresinin Float32Array olarak tanımlandığını fark edeceksiniz.
Sorgu parametresi EmbeddedResource'ta "gömmeler" ile aynı türde tanımlandığından TypeScript'te de aynı türe sahip olmalarını bekliyorum.
Neden farklı türlere dönüştürüldüklerini biliyorsanız, lütfen GitHub'daki Voy'a ulaşmaktan veya bir çekme isteği açmaktan çekinmeyin.
Voy , WebAssembly'deki açık kaynaklı bir anlamsal arama motorudur. Bunu, anlamsal özellikler geliştirmek ve dünyanın her yerindeki insanlar için daha iyi kullanıcı deneyimleri yaratmak amacıyla daha fazla projeye güç vermek için oluşturdum. Voy çeşitli tasarım ilkelerini takip eder:
- 🤏 Minik : Yavaş ağlara veya IoT'ye sahip mobil tarayıcılar gibi sınırlı cihazlar için yükü azaltın.
- 🚀 Hızlı : Kullanıcılar için en iyi arama deneyimini yaratın.
- 🌳 Sarsılabilir Ağaç : Paket boyutunu optimize edin ve Web Çalışanları gibi modern Web API'leri için eşzamansız yetenekleri etkinleştirin.
- 🔋 Devam Edilebilir : İstediğiniz zaman, istediğiniz yerde taşınabilir yerleştirme dizini oluşturun.
- ☁️ Dünya çapında : CDN uç sunucularında anlamsal bir arama çalıştırın.
Npm'de mevcuttur. Favori paket yöneticinizle kolayca kurabilirsiniz ve artık hazırsınız.
# with npm npm i voy-search # with Yarn yarn add voy-search # with pnpm pnpm add voy-search
Bir deneyin, sizden haber almaktan mutluluk duyuyorum!
Referanslar
- Yabancı fonksiyon arayüzü - Vikipedi
- serde-wasm-bindgen - GitHub
- Specta - GitHub
- Yapı wasm_bindgen::JsValue - wasm-bindgen
- Rust ve WebAssembly Kitabı - Rust ve WebAssembly
- ts-rs - GitHub
- Tsify - GitHub
- Yazı Paylaşımı - GitHub
- Voy - GitHub
- wasm-bindgen - Rust ve WebAssembly
- wasm-pack - Rust ve WebAssembly
- Rust'ta WASM Semantik Arama - Daw-Chih Liou
- WebAssembly - WebAssembly.org
Bağlanmak mı istiyorsunuz?
Bu makale ilk olarak Daw-Chih'in Web Sitesinde yayınlanmıştır.