Vamos.
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:
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.
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".
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.
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✨
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:
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ê!
Quer se conectar?
Este artigo foi publicado originalmente no site da Daw-Chih .