paint-brush
Como compartilhar tipos Rust com TypeScript para WebAssembly em 30 segundos: um guia rápidopor@dawchihliou
930 leituras
930 leituras

Como compartilhar tipos Rust com TypeScript para WebAssembly em 30 segundos: um guia rápido

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

Muito longo; Para ler

💡 Aprenderemos por que a cadeia de ferramentas oficial Rust e WebAssembly não é suficiente para TypeScript. 🤹 Mostrarei como gerar automaticamente a definição TypeScript com alteração mínima em seu código Rust. 🧪 Refatoraremos juntos uma biblioteca WebAssembly do mundo real no npm.
featured image - Como compartilhar tipos Rust com TypeScript para WebAssembly em 30 segundos: um guia rápido
Daw-Chih Liou HackerNoon profile picture

Descubra a experiência de desenvolvedor mais perfeita com Rust e WebAssembly. Esta é a maneira mais rápida de gerar automaticamente definições TypeScript a partir do seu código Rust.

Neste artigo

  • 💡 Aprenderemos por que a cadeia de ferramentas oficial Rust e WebAssembly não é suficiente para TypeScript.


  • 🤹 Mostrarei como gerar automaticamente a definição TypeScript com alteração mínima em seu código Rust.


  • 🧪 Refatoraremos juntos uma biblioteca WebAssembly do mundo real no npm.


Vamos.


O problema de digitação com wasm-bindgen

A geração de tipos TypeScript para módulos WebAssembly(Wasm) em Rust não é simples.


Encontrei o problema quando estava trabalhando em um mecanismo de pesquisa de similaridade vetorial no Wasm chamado Voy . Eu construí o mecanismo Wasm em Rust para fornecer aos engenheiros de JavaScript e TypeScript um canivete suíço para pesquisa semântica. Aqui está uma demonstração para a web:


Voy demonstração


Você pode encontrar o repositório do Voy no GitHub ! Sinta-se livre para experimentá-lo.


O repositório inclui exemplos que você pode ver como usar o Voy em diferentes frameworks.


Eu usei wasm-pack e wasm-bindgen para construir e compilar o código Rust para Wasm. As definições TypeScript geradas se parecem com isto:


 /* 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


Como você pode ver, há muitos tipos "qualquer", o que não é muito útil para a experiência do desenvolvedor. Vamos examinar o código Rust para descobrir o que aconteceu.


 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 }


A string, slice e unsigned integer geraram os tipos corretos no TypeScript, mas o " wasm_bindgen::JsValue " não. JsValue é a representação de wasm-bindgen de um objeto JavaScript.


Serializamos e desserializamos o JsValue para passá-lo entre JavaScript e Rust por meio do Wasm.


 #[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() }


É a abordagem oficial para converter tipos de dados, mas, evidentemente, precisamos ir além para oferecer suporte a TypeScript.

Gerar automaticamente ligação TypeScript com Tsify

A conversão de tipos de dados de um idioma para outro é, na verdade, um padrão comum chamado interface de função estrangeira (FFI). Eu explorei ferramentas FFI como Typeshare para gerar automaticamente definições TypeScript a partir de estruturas Rust, mas foi apenas metade da solução.


O que precisamos é de uma maneira de explorar a compilação Wasm e gerar a definição de tipo para a API do módulo Wasm. Assim:


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


Felizmente, o Tsify é uma incrível biblioteca de código aberto para o caso de uso. Tudo o que precisamos fazer é derivar do traço "Tsify" e adicionar uma macro #[tsify] às estruturas:


 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 }


É isso! Vamos dar uma olhada nos atributos "from_wasm_abi" e "into_wasm_abi".


Wasm ABI


Ambos os atributos convertem o tipo de dados Rust em definição TypeScript. O que eles fazem de diferente é a direção do fluxo de dados com a Application Binary Interface (ABI) do Wasm.


  • into_wasm_abi : os dados fluem de Rust para JavaScript. Usado para o tipo de retorno.


  • from_wasm_abi : os dados fluem de JavaScript para Rust. Usado para parâmetros.


Ambos os atributos usam serde-wasm-bindgen para implementar a conversão de dados entre Rust e JavaScript.


Estamos prontos para construir o módulo Wasm. Depois de executar "wasm-pack build", a definição de TypeScript gerada automaticamente:


 /* 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[] }


Todos os tipos "any" são substituídos pelas interfaces que definimos no código Rust✨

Pensamentos finais

Os tipos gerados parecem bons, mas existem algumas inconsistências. Se você olhar de perto, notará que o parâmetro de consulta na função de pesquisa é definido como Float32Array.


O parâmetro de consulta é definido como o mesmo tipo de "embeddings" em EmbeddedResource, portanto, espero que eles tenham o mesmo tipo em TypeScript.


Se você sabe por que eles são convertidos em tipos diferentes, não hesite em entrar em contato ou abrir uma solicitação pull no Voy no GitHub .


Voy é um mecanismo de pesquisa semântica de código aberto no WebAssembly. Eu o criei para capacitar mais projetos para construir recursos semânticos e criar melhores experiências de usuário para pessoas ao redor do mundo. Voy segue vários princípios de design:


  • 🤏 Tiny : Reduza a sobrecarga para dispositivos limitados, como navegadores móveis com redes lentas ou IoT.


  • 🚀 Rápido : crie a melhor experiência de pesquisa para os usuários.


  • 🌳 Tree Shakable : Otimize o tamanho do pacote e habilite recursos assíncronos para API da Web moderna, como Web Workers.


  • 🔋 Recuperável : gere índice de incorporação portátil em qualquer lugar, a qualquer hora.


  • ☁️ Em todo o mundo : execute uma pesquisa semântica em servidores de borda CDN.


Está disponível no npm. Você pode simplesmente instalá-lo com seu gerenciador de pacotes favorito e pronto.


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


Experimente e ficarei feliz em saber de você!

Referências


Quer se conectar?

Este artigo foi publicado originalmente no site da Daw-Chih .