WASM: compilando Rust para o navegador
1. Introdução ao WebAssembly com Rust
WebAssembly (WASM) revolucionou o desenvolvimento web ao permitir que linguagens como Rust executem código próximo à velocidade nativa dentro do navegador. Rust se destaca nesse cenário por três razões fundamentais:
- Desempenho: Sem garbage collector e com controle fino de memória, Rust produz binários WASM extremamente eficientes.
- Segurança: O sistema de ownership e borrow checker elimina classes inteiras de vulnerabilidades de memória.
- Ecossistema: Ferramentas como
wasm-pack,wasm-bindgenewasm-optformam um pipeline maduro e produtivo.
O fluxo de trabalho típico envolve: escrever Rust → compilar para wasm32-unknown-unknown → gerar bindings JavaScript → empacotar para o navegador. Ferramentas essenciais incluem:
- wasm-pack: Orquestra todo o processo de build, teste e publicação
- wasm-bindgen: Gera bridges entre Rust e JavaScript
- wasm-opt: Otimiza o binário WASM final
2. Configuração do ambiente e primeiro projeto
Primeiro, instale o target WASM:
rustup target add wasm32-unknown-unknown
Instale o wasm-pack:
cargo install wasm-pack
Crie um novo projeto:
wasm-pack new hello-wasm
cd hello-wasm
A estrutura gerada inclui:
hello-wasm/
├── Cargo.toml
├── src/
│ ├── lib.rs
│ └── utils.rs
├── pkg/ (gerado após build)
└── tests/
Configure o Cargo.toml para WASM:
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
[profile.release]
opt-level = "s" # Otimizar para tamanho
lto = true
3. Fundamentos do wasm-bindgen
wasm-bindgen é a peça central da interoperabilidade. Ele permite importar funções JavaScript e exportar funções Rust.
Exportando funções Rust para JavaScript
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Olá, {}! Bem-vindo ao WASM com Rust!", name)
}
Importando funções JavaScript
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn show_message() {
log("Função Rust chamada!");
alert("Isso veio do Rust!");
}
Manipulação de tipos complexos
use wasm_bindgen::JsValue;
#[wasm_bindgen]
pub fn process_data(data: &[u8]) -> Vec<u8> {
data.iter().map(|b| b.wrapping_add(1)).collect()
}
#[wasm_bindgen]
pub fn create_object() -> JsValue {
let obj = js_sys::Object::new();
js_sys::Reflect::set(&obj, &"name".into(), &"Rust WASM".into()).unwrap();
obj
}
4. Interação com o DOM e Web APIs
A crate web-sys fornece bindings para praticamente todas as APIs do navegador.
Adicione ao Cargo.toml:
[dependencies]
web-sys = { version = "0.3", features = [
"Document",
"Element",
"HtmlElement",
"Window",
"console",
"CanvasRenderingContext2d",
"HtmlCanvasElement",
] }
Manipulando o DOM
use wasm_bindgen::prelude::*;
use web_sys::{window, document, HtmlElement};
#[wasm_bindgen]
pub fn change_dom() {
let document = document().unwrap();
let body = document.body().unwrap();
let div = document.create_element("div").unwrap();
div.set_inner_html("<h1>Rust WASM no DOM!</h1>");
body.append_child(&div).unwrap();
}
#[wasm_bindgen]
pub fn setup_click_handler() {
let document = document().unwrap();
let button = document.create_element("button").unwrap();
button.set_inner_html("Clique em Rust");
let closure = Closure::wrap(Box::new(move || {
let window = window().unwrap();
window.alert_with_message("Botão Rust clicado!").unwrap();
}) as Box<dyn Fn()>);
button
.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())
.unwrap();
closure.forget();
document.body().unwrap().append_child(&button).unwrap();
}
Timers e console.log
use wasm_bindgen::prelude::*;
use web_sys::console;
#[wasm_bindgen]
pub fn start_timer() {
let closure = Closure::wrap(Box::new(move || {
console::log_1(&"Tick do Rust!".into());
}) as Box<dyn Fn()>);
window()
.unwrap()
.set_interval_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
1000,
)
.unwrap();
closure.forget();
}
5. Performance e otimizações
Usando wee_alloc para reduzir alocações
Adicione ao Cargo.toml:
[dependencies]
wee_alloc = { version = "0.4", features = ["size_classes"] }
[features]
default = ["wee_alloc"]
[profile.release]
opt-level = "z" # Otimizar agressivamente para tamanho
No lib.rs:
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
Compilação otimizada
# Build de produção
wasm-pack build --release
# Otimização adicional com wasm-opt
wasm-opt -Oz pkg/hello_wasm_bg.wasm -o pkg/hello_wasm_opt.wasm
Medindo o binário
# Verificar tamanho
ls -lh pkg/*.wasm
# Usar twiggy para análise
cargo install twiggy
twiggy top pkg/hello_wasm_bg.wasm
6. Exemplo prático: Canvas 2D com animação
Implementando um visualizador de partículas:
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, window};
struct Particle {
x: f64,
y: f64,
vx: f64,
vy: f64,
radius: f64,
color: String,
}
#[wasm_bindgen]
pub fn start_particles(canvas_id: &str) {
let document = web_sys::document().unwrap();
let canvas = document
.get_element_by_id(canvas_id)
.unwrap()
.dyn_into::<HtmlCanvasElement>()
.unwrap();
let context = canvas
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<CanvasRenderingContext2d>()
.unwrap();
let mut particles: Vec<Particle> = (0..100)
.map(|_| Particle {
x: js_sys::Math::random() * canvas.width() as f64,
y: js_sys::Math::random() * canvas.height() as f64,
vx: (js_sys::Math::random() - 0.5) * 4.0,
vy: (js_sys::Math::random() - 0.5) * 4.0,
radius: js_sys::Math::random() * 5.0 + 2.0,
color: format!(
"hsl({}, 70%, 50%)",
(js_sys::Math::random() * 360.0) as u32
),
})
.collect();
let f: Rc<RefCell<Option<Closure<dyn Fn()>>>> = Rc::new(RefCell::new(None));
let g = f.clone();
*g.borrow_mut() = Some(Closure::wrap(Box::new(move || {
context.clear_rect(
0.0,
0.0,
canvas.width() as f64,
canvas.height() as f64,
);
for particle in &mut particles {
particle.x += particle.vx;
particle.y += particle.vy;
if particle.x < 0.0 || particle.x > canvas.width() as f64 {
particle.vx = -particle.vx;
}
if particle.y < 0.0 || particle.y > canvas.height() as f64 {
particle.vy = -particle.vy;
}
context.set_fill_style(&particle.color.as_str().into());
context.begin_path();
context
.arc(particle.x, particle.y, particle.radius, 0.0,
std::f64::consts::PI * 2.0)
.unwrap();
context.fill();
}
window()
.unwrap()
.request_animation_frame(f.borrow().as_ref().unwrap().as_ref().unchecked_ref())
.unwrap();
}) as Box<dyn Fn()>));
window()
.unwrap()
.request_animation_frame(g.borrow().as_ref().unwrap().as_ref().unchecked_ref())
.unwrap();
}
7. Publicação e integração com ferramentas modernas
Integração com Webpack
Instale o plugin:
npm install --save-dev @wasm-tool/wasm-pack-plugin
Configure no webpack.config.js:
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
plugins: [
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, "."),
outDir: path.resolve(__dirname, "pkg"),
}),
],
experiments: {
asyncWebAssembly: true,
},
};
Publicando no npm
wasm-pack build --release --scope meu-usuario
wasm-pack publish
Considerações de CSP
Para usar WASM com Content Security Policy, adicione:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-eval';">
Ou, mais restritivamente:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval';">
WebAssembly com Rust oferece um caminho sólido para levar código de alto desempenho ao navegador. Com ferramentas maduras como wasm-pack e um ecossistema crescente de crates como web-sys e js-sys, você pode construir desde pequenos componentes interativos até aplicações completas que rodam com eficiência próxima à nativa. O futuro do desenvolvimento web certamente passará por WASM, e Rust está na vanguarda dessa revolução.
Referências
- The
wasm-packGuide — Documentação oficial da ferramenta principal para build e publicação de projetos Rust-WASM - wasm-bindgen Documentation — Guia completo sobre como gerar bindings entre Rust e JavaScript
- web-sys crate documentation — Documentação da crate que fornece bindings para todas as APIs do navegador
- Rust and WebAssembly Book — Livro oficial da Mozilla sobre desenvolvimento Rust-WASM, cobrindo desde conceitos básicos até otimizações avançadas
- MDN WebAssembly Concepts — Artigo da MDN explicando os fundamentos do WebAssembly e como ele se integra com a web
- wasm-opt documentation — Ferramenta de otimização de binários WASM, essencial para reduzir tamanho e melhorar performance
- WebAssembly Performance Guide — Guia de performance do ecossistema Rust-WASM, com dicas para evitar alocações e medir eficiência