WebAssembly com Rust para o navegador: compilar e usar com JS (iniciantes)
Por John Pires · 19 de março de 2026

Contexto: quando voce vai usar isso
Motivacao
WebAssembly (Wasm) e uma tecnologia que traz codigo compilado para rodar no navegador com desempenho muito superior ao JavaScript em tarefas numericas e de processamento intensivo. Para quem esta começando, o uso mais comum e descarregar do JS partes que fazem processamento de texto, tokenizacao, calculos matriciais simples ou inferencia leve — ou seja, tarefas que aparecem em pipelines de IA e pre-processamento.
Casos de uso práticos
Voce vai escolher compilar Rust para Wasm quando precisar de: performance deterministica, melhor controle de memoria e bibliotecas existentes em Rust/C que implementam algoritmos de NLP, ML ou sinais. Exemplos: uma tokenizacao customizada, um calculo de scoring de tokens, ou aceleracao de transformacoes de vetores antes de enviar para um modelo.
Explicacao visual com diagramas mentais
Como o fluxo funciona (visao geral)
Browser (JS) <---> WebAssembly (Wasm, compilado de Rust)
| |
DOM / UI algoritmos numericos
Rede / Fetch -> dados brutos tokenizacao / scoring
Imagine o navegador como uma cozinha: o JavaScript e o cozinheiro que organiza, monta e apresenta os pratos. O WebAssembly e a faca profissional e a panela de alta pressao: quando o corte fino ou o cozimento rapido sao críticos, voce usa o Wasm (escrito em Rust) para fazer esse trabalho e volta para o JS apresentar o resultado.
Dados e fronteiras (memoria e tipos)
Wasm tem sua propria area de memoria linear. A ponte entre JS e Wasm pode usar tipos simples (inteiros, floats) e também arrays tipados (Float32Array). Quando voce envia vetores numericos do JS para o Wasm, o ideal e usar TypedArray e funções expostas por wasm-bindgen que aceitam Box<[f32]> ou converter via js_sys::Float32Array. Isso evita copias desnecessarias e garante desempenho.
Primeiro exemplo simples
O que vamos construir
Exemplo prático: um crate Rust que expõe uma função score_tokens que recebe dois vetores de floats (entrada e pesos) e calcula o produto escalar. Esse calculo e comum em inferencia leve: pontuar tokens, aplicar um bias ou calcular um neurônio simples. O objetivo e aprender o fluxo de compilacao e consumo no navegador.
Passo a passo: estrutura do projeto
Os passos basicos: criar um crate de biblioteca Rust, adicionar wasm-bindgen, compilar com wasm-pack, e consumir o pacote gerado no frontend com um bundle ou diretamente como modulo ES6. Mostrarei os arquivos essenciais.
Arquivo Cargo.toml (declara dependencia wasm-bindgen). Isso configura o crate para gerar um cdylib que o wasm-pack vai empacotar.
[package]
name = "wasm_token"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
O que este arquivo faz: configura o crate e declara wasm-bindgen como dependencia. Depois de compilado com wasm-pack, voce tera uma pasta pkg com arquivos JS e .wasm prontos para importar.
Codigo Rust: src/lib.rs. Esta funcao recebe dois arrays de floats (Box<[f32]>) e calcula produto escalar. Usamos wasm-bindgen para expor para JS.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn score_tokens(input: Box<[f32]>, weights: Box<[f32]>) -> f32 {
let len = std::cmp::min(input.len(), weights.len());
let mut sum: f32 = 0.0;
for i in 0..len {
sum += input[i] * weights[i];
}
sum
}
O que acontece quando esta funcao e chamada: o Wasm recebe duas arrays alocadas a partir do JS (wasm-bindgen cuida da conversao). Ela calcula o produto escalar e retorna um f32 simples. Esse valor pode ser usado no JS para decidir ranking, threshold ou enviar para outro passo do pipeline.
Comandos para compilar (assumindo wasm-pack instalado e Rust configurado):
wasm-pack build --target web
O comando gera um pacote em pkg que voce pode importar em um projeto frontend moderno via import. Depois, no frontend, voce cria um Float32Array e passa para a funcao exposta.
Exemplo minimal de uso no navegador (index.html + index.js). A logica JS cria buffers, chama score_tokens e imprime o resultado no console.
<!doctype html>
<html>
<body>
<script type='module' src='index.js'></script>
</body>
</html>
// index.js
import init, { score_tokens } from './pkg/wasm_token.js';
async function run(){
await init();
const input = new Float32Array([0.2, 0.7, 0.1]);
const weights = new Float32Array([0.5, 0.3, 0.2]);
const result = score_tokens(input, weights);
console.log('score', result);
}
run();
O que ocorre aqui: init inicializa o runtime Wasm gerado pelo wasm-pack. Depois chamamos score_tokens com Float32Array; wasm-bindgen converte para Box<[f32]>, o calculo roda no Wasm e retorna um f32 para o JS.
Segundo exemplo mais realista
Objetivo: tokenizacao simples e pontuacao
Agora vamos implementar algo mais diretamente util para pipelines de IA: uma tokenizacao simples em Rust que normaliza texto, separa tokens e calcula uma pontuacao (por exemplo, contagem ponderada). A ideia e fazer o pre-processamento no Wasm e retornar os tokens e scores para o JS para exibir ou enviar ao modelo.
Codigo Rust: tokenizer com contagem ponderada
Este exemplo expoe duas funcoes: tokenize(text) -> Vec<String> e token_scores(tokens, weights) -> Vec<f32>. Para interoperar com JS, usamos wasm-bindgen e retornamos um JsValue com um array de strings e um Vector de floats.
use wasm_bindgen::prelude::*;
use js_sys::Array;
#[wasm_bindgen]
pub fn tokenize(text: &str) -> Array {
let mut arr = Array::new();
for token in text
.to_lowercase()
.split(|c: char| !c.is_alphanumeric())
.filter(|s| !s.is_empty())
{
arr.push(&JsValue::from_str(token));
}
arr
}
#[wasm_bindgen]
pub fn token_scores(tokens: Box<[JsValue]>, weights: Box<[f32]>) -> Box<[f32]> {
let mut out = Vec::with_capacity(tokens.len());
for (i, t) in tokens.iter().enumerate() {
let len = t.as_string().unwrap_or_default().len() as f32;
let w = if i < weights.len() { weights[i] } else { 1.0 };
out.push(len * w);
}
out.into_boxed_slice()
}
O que faz: tokenize retorna um Array JS de tokens (strings). token_scores recebe esse array convertido em Box<[JsValue]> e um vetor de pesos, calculando uma pontuacao simples (comprimento do token vezes peso) e retorna como Box<[f32]> para o JS receber um TypedArray.
Exemplo de uso JS que chama tokenize e token_scores e renderiza um pequeno JSON no console:
import init, { tokenize, token_scores } from './pkg/wasm_token.js';
async function run(){
await init();
const text = 'O modelo recebe tokens e calcula scores.';
const tokens = tokenize(text); // Array de strings do Rust
const weights = new Float32Array([1.0, 0.5, 0.8, 1.2, 1.0, 0.9]);
// converter tokens para um Box<[JsValue]> e passar para token_scores funciona automatico via wasm-bindgen
const scores = token_scores(tokens, weights);
console.log('tokens', tokens);
console.log('scores', scores);
}
run();
Comportamento: este fluxo empurra a logica de parsing e parte do calculo para Rust/Wasm. O Javascript permanece responsavel por UI, fetch de dados e orquestracao.
Erros comuns de iniciante e como evitar
Erro: tipos incompatíveis e copias desnecessarias
Uma armadilha frequente: passar arrays JS normais (Array) para funções Wasm esperando TypedArray. Isso causa copias ou erros. Use Float32Array/Int32Array no JS e tipos correspondentes em Rust (Box<[f32]>, Box<[i32]>). Evite criar strings gigantescas e fazer parsing pesado no JS se voce planeja mover essa tarefa para Rust.
Erro: esquecimento do wasm-pack init/target
Outro erro comum e compilar sem o target correto. Para usar diretamente no navegador, rode wasm-pack build --target web. Para uso com bundlers, use "bundler" como target. Se voce importar no Node.js, use "nodejs" e tenha atencao ao runtime.
Tip: durante desenvolvimento, use um servidor local (por exemplo, serve, http-server ou o próprio dev server do seu bundler). Abrir o HTML com file:// frequentemente causa problemas de carregamento de .wasm.
Erro: esquecer de inicializar o módulo Wasm
Many packages generated by wasm-pack export an init function. Always await init() antes de chamar funções expostas, caso contrario ocorrerao erros porque o .wasm ainda nao foi carregado. Em bundlers modernos voce importa o pkg e chama await init();
Dicas práticas para debugging
- Habilite a ferramenta de DevTools para WebAssembly no navegador (Chrome/Firefox têm visores de memória).
- Use console.log apenas no JS; dentro do Rust, use console_error_panic_hook::set_once() para obter panic messages legíveis no console.
- Testes unitarios: escreva testes em Rust para a logica antes de compilar para Wasm. Testes reduzem tempo de iteracao.
Conclusao prática
Rust + WebAssembly e uma combinação poderosa para acelerar partes criticas de um pipeline de IA no frontend: tokenizacao, transformacoes, scoring e outras rotinas numericas. Aprenda a mover apenas a logica que precisa de performance para Wasm e manter JS como orquestrador e UI.
Resumo de passos práticos:
- Escreva a logica em Rust e exponha funcoes com #[wasm_bindgen].
- Compile com wasm-pack --target web para uso no navegador.
- No frontend, importe o pacote gerado, chame await init() e passe TypedArrays (Float32Array) quando trabalhar com vetores numericos.
Se voce esta começando: comece com o exemplo de produto escalar, veja como os dados fluem e depois migre tokenizacao ou outras transformacoes. Evite mover lógicas triviais que nao beneficiam de performance. Por fim, pratique incrementalmente: compile, importe, teste e use DevTools para inspecionar o .wasm.
Implementar e consumir Wasm com Rust leva tempo para pegar o ritmo, mas as vantagens em cenarios de IA no navegador costumam compensar: mais velocidade, menor latencia e controle sobre o comportamento numerico.
Assuntos: Rust, webassembly, wasm, JavaScript, frontend
Leia também
Artigos SEO-friendly: HTML semântico, JSON-LD e Web Components indexáveis
Demonstração prática de como combinar HTML semântico, JSON-LD e Web Components indexáveis para otimizar artigos: exemplos reais, patterns e armadilhas para…
CSV streaming no browser com Web Workers, Transferable Streams e AbortSignal
Como processar CSV grandes no browser sem travar a UI: use Web Workers para parsing, transfira ReadableStreams para o main thread e implemente abort…
Debounce e Throttle em JavaScript: cancelar fetchs com AbortController
Aprenda, passo a passo, a usar debounce e throttle com AbortController para cancelar fetchs em JavaScript. Evite race conditions, reduza tráfego e mantenha…
Retry resiliente com fetch: AbortController, backoff exponencial e streaming
Padrões práticos para retries resilientes com fetch: combine AbortController para cancelamento, backoff exponencial com jitter para evitar picos, e streaming…
Criando um simulador interativo do Event Loop: Call Stack, Tasks e Microtasks
Aprenda, do zero, como call stack, microtasks e tasks interagem no event loop. Construa um simulador interativo que demonstra a ordem de execução, evite…
Pool de concorrência com Async Iterators e AbortSignal
Implementar um pool de concorrência com Async Iterators e AbortSignal para controlar tarefas assíncronas, permitir cancelamento cooperativo e aplicar…