---
title: "wasm-bindgen Rust: WebAssembly, JavaScript e Browser em 2026"
url: "https://rustlang.com.br/ecossistema/wasm-bindgen/"
markdown_url: "https://rustlang.com.br/ecossistema/wasm-bindgen.MD"
description: "Guia completo de wasm-bindgen em Rust: WebAssembly no browser, integração com JavaScript, web-sys, js-sys, wasm-pack, TypeScript, performance e carreira frontend/backend."
date: ""
author: ""
---

# wasm-bindgen Rust: WebAssembly, JavaScript e Browser em 2026

Guia completo de wasm-bindgen em Rust: WebAssembly no browser, integração com JavaScript, web-sys, js-sys, wasm-pack, TypeScript, performance e carreira frontend/backend.


WebAssembly (Wasm) é um formato binário que permite executar código de alta performance diretamente no navegador (e fora dele). A crate `wasm-bindgen` é a ponte que conecta Rust ao mundo JavaScript, permitindo que funções Rust sejam chamadas de JS e vice-versa, com conversão automática de tipos.

Rust é uma das linguagens mais bem posicionadas para WebAssembly: sem garbage collector, sem runtime pesado, com controle fino de memória e performance próxima ao código nativo. Com `wasm-bindgen`, `web-sys` e `wasm-pack`, você tem um ecossistema completo para criar desde bibliotecas de processamento de dados até aplicações web interativas.

## wasm-bindgen Rust em 2026: onde ele encaixa

Quem busca **wasm-bindgen Rust** normalmente quer responder uma pergunta prática: como levar código Rust para uma aplicação web sem reescrever todo o frontend? A resposta curta é que `wasm-bindgen` funciona melhor como camada de interoperabilidade. Você mantém React, Vue, Svelte, JavaScript ou TypeScript onde eles são fortes, e move para Rust as partes que precisam de performance previsível, segurança de memória, processamento local ou reutilização de uma biblioteca existente.

Os casos mais comuns são filtros de imagem, compressão, criptografia, simulação, parsers, editores, visualização de dados, plugins e ferramentas que rodam no browser. Para uma visão ampla do tema, veja também [Rust e WebAssembly em 2026](/blog/rust-webassembly-wasm-2026/) e o tutorial passo a passo de [WebAssembly com Rust](/tutoriais/webassembly-rust/). Se o foco for aplicação web de alta performance, o guia [Rust e WebAssembly: apps web de alta performance](/blog/rust-webassembly-2026-aplicacoes-web/) complementa esta referência com exemplos de produto.

Para carreira, o sinal forte não é "usar WASM em tudo". O sinal forte é saber explicar quando Rust no browser vale a complexidade: gargalos reais, código compartilhado, fronteira clara com JavaScript, testes, bundle size, observabilidade e fallback. Combine `wasm-bindgen` com [Serde](/ecossistema/serde/) para contratos de dados, [Criterion](/ecossistema/criterion/) para benchmark, [Cargo](/ecossistema/cargo/) para empacotamento e [Clippy](/ecossistema/clippy/) para manter o módulo confiável. Depois conecte isso a um projeto de portfólio e acompanhe as [vagas Rust](/vagas/) e [empresas que usam Rust](/empresas/) em áreas como segurança, fintech, ferramentas para devs, dados e edge computing.

## Instalação

### Pré-requisitos

Instale as ferramentas necessárias:

```bash
# Target de compilação WASM
rustup target add wasm32-unknown-unknown

# wasm-pack: ferramenta de build e publicação
cargo install wasm-pack

# Opcional: servidor de desenvolvimento
cargo install miniserve
```

### Criando um Projeto WASM

```bash
# Usando wasm-pack template
cargo init meu-projeto-wasm --lib
cd meu-projeto-wasm
```

Configure o `Cargo.toml`:

```toml
[package]
name = "meu-projeto-wasm"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = [
    "console",
    "Document",
    "Element",
    "HtmlElement",
    "HtmlCanvasElement",
    "CanvasRenderingContext2d",
    "Window",
    "Node",
    "Event",
    "MouseEvent",
    "KeyboardEvent",
    "HtmlInputElement",
    "CssStyleDeclaration",
    "DomTokenList",
] }

[profile.release]
opt-level = "s"      # Otimizar para tamanho
lto = true           # Link-Time Optimization
```

## Uso Básico

### Exportando Funções para JavaScript

```rust
// src/lib.rs
use wasm_bindgen::prelude::*;

// Exportar uma função simples
#[wasm_bindgen]
pub fn somar(a: i32, b: i32) -> i32 {
    a + b
}

// Exportar com nome diferente em JS
#[wasm_bindgen(js_name = cumprimentar)]
pub fn saudar(nome: &str) -> String {
    format!("Olá, {}! Bem-vindo ao Rust + WASM!", nome)
}

// Função que recebe e retorna tipos complexos
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> Vec<u32> {
    let mut seq = vec![0, 1];
    for i in 2..n as usize {
        let proximo = seq[i - 1] + seq[i - 2];
        seq.push(proximo);
    }
    seq
}

// Função que pode falhar
#[wasm_bindgen]
pub fn dividir(a: f64, b: f64) -> Result<f64, JsValue> {
    if b == 0.0 {
        Err(JsValue::from_str("Divisão por zero!"))
    } else {
        Ok(a / b)
    }
}
```

### Compilando e Usando

```bash
# Compilar com wasm-pack
wasm-pack build --target web

# Isso gera a pasta pkg/ com:
# - meu_projeto_wasm_bg.wasm  (binário WASM)
# - meu_projeto_wasm.js       (glue code JS)
# - meu_projeto_wasm.d.ts     (tipos TypeScript)
# - package.json
```

Usando no HTML:

```html
<!DOCTYPE html>
<html>
<head>
    <title>Rust + WASM</title>
</head>
<body>
    <h1>Rust WebAssembly</h1>
    <div id="resultado"></div>

    <script type="module">
        import init, { somar, cumprimentar, fibonacci, dividir }
            from './pkg/meu_projeto_wasm.js';

        async function main() {
            await init();

            console.log("2 + 3 =", somar(2, 3));
            console.log(cumprimentar("Maria"));
            console.log("Fibonacci:", fibonacci(10));

            try {
                console.log("10 / 3 =", dividir(10, 3));
                console.log("10 / 0 =", dividir(10, 0));
            } catch (e) {
                console.error("Erro:", e);
            }

            document.getElementById("resultado").textContent =
                cumprimentar("Mundo");
        }

        main();
    </script>
</body>
</html>
```

### Importando Funções JavaScript

```rust
use wasm_bindgen::prelude::*;

// Importar funções globais do JS
#[wasm_bindgen]
extern "C" {
    // window.alert()
    fn alert(s: &str);

    // console.log()
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);

    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_u32(a: u32);

    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_many(a: &str, b: &str);

    // performance.now()
    #[wasm_bindgen(js_namespace = performance)]
    fn now() -> f64;
}

// Macro auxiliar para console.log
macro_rules! console_log {
    ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}

#[wasm_bindgen]
pub fn demonstrar_imports() {
    console_log!("Olá do Rust via console.log!");
    console_log!("Performance.now(): {:.2}ms", now());
    log_many("Múltiplos", "argumentos");
}
```

### Structs Exportadas

```rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct Contador {
    valor: i32,
    historico: Vec<i32>,
}

#[wasm_bindgen]
impl Contador {
    #[wasm_bindgen(constructor)]
    pub fn novo(valor_inicial: i32) -> Contador {
        Contador {
            valor: valor_inicial,
            historico: vec![valor_inicial],
        }
    }

    pub fn incrementar(&mut self) {
        self.valor += 1;
        self.historico.push(self.valor);
    }

    pub fn decrementar(&mut self) {
        self.valor -= 1;
        self.historico.push(self.valor);
    }

    pub fn valor(&self) -> i32 {
        self.valor
    }

    pub fn resetar(&mut self, valor: i32) {
        self.valor = valor;
        self.historico.clear();
        self.historico.push(valor);
    }

    pub fn historico_json(&self) -> String {
        format!("{:?}", self.historico)
    }
}
```

Uso em JavaScript:

```javascript
import { Contador } from './pkg/meu_projeto_wasm.js';

const contador = new Contador(0);
contador.incrementar();
contador.incrementar();
contador.incrementar();
contador.decrementar();
console.log("Valor:", contador.valor());       // 2
console.log("Histórico:", contador.historico_json()); // [0, 1, 2, 3, 2]
contador.free(); // Liberar memória WASM
```

## Recursos Avançados

### web-sys: APIs do Navegador

A crate `web-sys` fornece bindings para todas as Web APIs:

```rust
use wasm_bindgen::prelude::*;
use web_sys::{console, Document, Element, HtmlElement, Window};

fn window() -> Window {
    web_sys::window().expect("Sem window global")
}

fn document() -> Document {
    window().document().expect("Sem document")
}

#[wasm_bindgen]
pub fn manipular_dom() -> Result<(), JsValue> {
    let document = document();

    // Criar elemento
    let div = document.create_element("div")?;
    div.set_id("meu-div");
    div.set_class_name("container ativo");
    div.set_inner_html("<h2>Criado pelo Rust!</h2><p>Manipulação de DOM via wasm-bindgen.</p>");

    // Estilizar
    let estilo = div
        .dyn_ref::<HtmlElement>()
        .unwrap()
        .style();
    estilo.set_property("background-color", "#f0f0f0")?;
    estilo.set_property("padding", "20px")?;
    estilo.set_property("border-radius", "8px")?;
    estilo.set_property("margin", "10px")?;

    // Adicionar ao body
    let body = document.body().unwrap();
    body.append_child(&div)?;

    // Buscar elementos existentes
    if let Some(titulo) = document.get_element_by_id("titulo") {
        titulo.set_text_content(Some("Título atualizado pelo Rust!"));
    }

    // Query selector
    let paragrafos = document.query_selector_all("p")?;
    console::log_1(&format!("Encontrados {} parágrafos", paragrafos.length()).into());

    Ok(())
}
```

### Event Listeners com Closures

```rust
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{Document, Event, HtmlInputElement, MouseEvent};

#[wasm_bindgen]
pub fn configurar_eventos() -> Result<(), JsValue> {
    let document = web_sys::window().unwrap().document().unwrap();

    // Evento de clique
    let botao = document.get_element_by_id("meu-botao").unwrap();
    let callback = Closure::wrap(Box::new(move |evento: MouseEvent| {
        let x = evento.client_x();
        let y = evento.client_y();
        web_sys::console::log_1(
            &format!("Clique em ({}, {})", x, y).into(),
        );
    }) as Box<dyn FnMut(MouseEvent)>);

    botao.add_event_listener_with_callback(
        "click",
        callback.as_ref().unchecked_ref(),
    )?;
    callback.forget(); // Manter o closure vivo

    // Evento de input
    let input = document.get_element_by_id("meu-input").unwrap();
    let output = document.get_element_by_id("output").unwrap();

    let callback_input = Closure::wrap(Box::new(move |evento: Event| {
        let target = evento.target().unwrap();
        let input = target.dyn_ref::<HtmlInputElement>().unwrap();
        let valor = input.value();
        let maiusculo = valor.to_uppercase();

        if let Some(el) = web_sys::window()
            .unwrap()
            .document()
            .unwrap()
            .get_element_by_id("output")
        {
            el.set_text_content(Some(&maiusculo));
        }
    }) as Box<dyn FnMut(Event)>);

    input.add_event_listener_with_callback(
        "input",
        callback_input.as_ref().unchecked_ref(),
    )?;
    callback_input.forget();

    Ok(())
}
```

### js-sys: Tipos JavaScript Nativos

```rust
use wasm_bindgen::prelude::*;
use js_sys::{Array, Date, JSON, Map, Object, Promise, Reflect};

#[wasm_bindgen]
pub fn demonstrar_js_sys() -> Result<JsValue, JsValue> {
    // Date
    let agora = Date::new_0();
    web_sys::console::log_1(&format!(
        "Data JS: {}/{}/{}",
        agora.get_date(),
        agora.get_month() + 1,
        agora.get_full_year()
    ).into());

    // Array
    let arr = Array::new();
    arr.push(&JsValue::from("Rust"));
    arr.push(&JsValue::from("WebAssembly"));
    arr.push(&JsValue::from("JavaScript"));
    web_sys::console::log_1(&format!("Array length: {}", arr.length()).into());

    // Map
    let mapa = Map::new();
    mapa.set(&JsValue::from("linguagem"), &JsValue::from("Rust"));
    mapa.set(&JsValue::from("versao"), &JsValue::from("1.75"));

    // Object
    let obj = Object::new();
    Reflect::set(&obj, &"nome".into(), &"wasm-bindgen".into())?;
    Reflect::set(&obj, &"versao".into(), &"0.2".into())?;

    // JSON
    let json_str = JSON::stringify(&obj)?;
    web_sys::console::log_1(&json_str);

    Ok(obj.into())
}
```

### Processamento de Dados Pesado

Um dos melhores casos de uso para WASM: processamento intensivo de dados:

```rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn processar_imagem(dados: &[u8], largura: u32, altura: u32) -> Vec<u8> {
    let mut resultado = dados.to_vec();

    // Converter para escala de cinza
    for pixel in resultado.chunks_exact_mut(4) {
        let r = pixel[0] as f32;
        let g = pixel[1] as f32;
        let b = pixel[2] as f32;
        let cinza = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
        pixel[0] = cinza;
        pixel[1] = cinza;
        pixel[2] = cinza;
        // pixel[3] (alpha) permanece inalterado
    }

    resultado
}

#[wasm_bindgen]
pub fn aplicar_blur(dados: &[u8], largura: u32, altura: u32, raio: u32) -> Vec<u8> {
    let mut resultado = dados.to_vec();
    let w = largura as usize;
    let h = altura as usize;
    let r = raio as usize;

    for y in r..h - r {
        for x in r..w - r {
            let mut soma_r = 0u32;
            let mut soma_g = 0u32;
            let mut soma_b = 0u32;
            let mut contagem = 0u32;

            for dy in 0..=2 * r {
                for dx in 0..=2 * r {
                    let ny = y + dy - r;
                    let nx = x + dx - r;
                    let idx = (ny * w + nx) * 4;
                    soma_r += dados[idx] as u32;
                    soma_g += dados[idx + 1] as u32;
                    soma_b += dados[idx + 2] as u32;
                    contagem += 1;
                }
            }

            let idx = (y * w + x) * 4;
            resultado[idx] = (soma_r / contagem) as u8;
            resultado[idx + 1] = (soma_g / contagem) as u8;
            resultado[idx + 2] = (soma_b / contagem) as u8;
        }
    }

    resultado
}

// Sorting eficiente em WASM
#[wasm_bindgen]
pub fn ordenar_numeros(mut numeros: Vec<f64>) -> Vec<f64> {
    numeros.sort_by(|a, b| a.partial_cmp(b).unwrap());
    numeros
}

// Busca em texto (muito mais rápido que JS para textos grandes)
#[wasm_bindgen]
pub fn contar_ocorrencias(texto: &str, padrao: &str) -> u32 {
    texto.matches(padrao).count() as u32
}
```

### Workflow com wasm-pack

```bash
# Build para web (ES modules)
wasm-pack build --target web

# Build para bundlers (webpack, vite, etc.)
wasm-pack build --target bundler

# Build para Node.js
wasm-pack build --target nodejs

# Build com otimizações
wasm-pack build --release --target web

# Testar no navegador
wasm-pack test --chrome --headless

# Publicar no npm
wasm-pack publish
```

Estrutura do `pkg/` gerado:

```
pkg/
├── meu_projeto_wasm_bg.wasm    # Binário WASM
├── meu_projeto_wasm_bg.wasm.d.ts # Tipos do WASM
├── meu_projeto_wasm.js          # Glue code JS
├── meu_projeto_wasm.d.ts        # Tipos TypeScript
├── package.json                  # Para npm
└── README.md
```

## Boas Práticas

### 1. Minimize as Chamadas entre JS e WASM

```rust
use wasm_bindgen::prelude::*;

// RUIM: muitas chamadas individuais JS -> WASM
#[wasm_bindgen]
pub fn somar_um(n: f64) -> f64 {
    n + 1.0
}
// JS: for (let i = 0; i < 1000; i++) { arr[i] = somar_um(arr[i]); }

// BOM: processar em lote
#[wasm_bindgen]
pub fn somar_um_lote(dados: &[f64]) -> Vec<f64> {
    dados.iter().map(|n| n + 1.0).collect()
}
// JS: arr = somar_um_lote(arr);
```

### 2. Gerencie Memória Corretamente

```rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct RecursoPesado {
    dados: Vec<u8>,
}

#[wasm_bindgen]
impl RecursoPesado {
    #[wasm_bindgen(constructor)]
    pub fn novo(tamanho: usize) -> RecursoPesado {
        RecursoPesado {
            dados: vec![0; tamanho],
        }
    }

    // Método free() é gerado automaticamente pelo wasm-bindgen
    // JS deve chamar recurso.free() quando não precisar mais
}
```

```javascript
// Uso correto em JS:
const recurso = new RecursoPesado(1024 * 1024);
// ... usar recurso ...
recurso.free(); // IMPORTANTE: liberar memória WASM
```

### 3. Use #[wasm_bindgen(start)] para Inicialização

```rust
use wasm_bindgen::prelude::*;

#[wasm_bindgen(start)]
pub fn inicializar() {
    // Configurar panic hook para mensagens de erro úteis
    #[cfg(feature = "console_error_panic_hook")]
    console_error_panic_hook::set_once();

    web_sys::console::log_1(&"Módulo WASM inicializado!".into());
}
```

### 4. Otimize o Tamanho do WASM

```toml
# Cargo.toml
[profile.release]
opt-level = "s"       # Otimizar para tamanho (ou "z" para menor possível)
lto = true            # Link-Time Optimization
codegen-units = 1     # Melhor otimização
strip = true          # Remover símbolos de debug

[dependencies]
console_error_panic_hook = { version = "0.1", optional = true }
wee_alloc = { version = "0.4", optional = true }

[features]
default = ["console_error_panic_hook"]
```

```bash
# Usar wasm-opt para otimização adicional (instalado com wasm-pack)
wasm-pack build --release
```

### 5. Tratamento de Erros Informativo

```rust
use wasm_bindgen::prelude::*;

// Configurar panic hook para erros legíveis no console do navegador
pub fn configurar_panic_hook() {
    #[cfg(feature = "console_error_panic_hook")]
    console_error_panic_hook::set_once();
}

#[wasm_bindgen]
pub fn operacao_que_pode_falhar(dados: &str) -> Result<String, JsValue> {
    configurar_panic_hook();

    serde_json::from_str::<serde_json::Value>(dados)
        .map(|v| v.to_string())
        .map_err(|e| JsValue::from_str(&format!("Erro de parsing: {}", e)))
}
```

## Exemplos Práticos

### Exemplo Completo: Componente Web Interativo

```rust
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{
    CanvasRenderingContext2d, Document, HtmlCanvasElement,
    HtmlElement, MouseEvent, Window,
};
use std::cell::RefCell;
use std::rc::Rc;

fn window() -> Window {
    web_sys::window().unwrap()
}

fn document() -> Document {
    window().document().unwrap()
}

#[wasm_bindgen]
pub struct App {
    canvas: HtmlCanvasElement,
    ctx: CanvasRenderingContext2d,
}

#[derive(Clone)]
struct Ponto {
    x: f64,
    y: f64,
    cor: String,
    raio: f64,
}

#[wasm_bindgen]
impl App {
    #[wasm_bindgen(constructor)]
    pub fn novo(canvas_id: &str) -> Result<App, JsValue> {
        let document = document();
        let canvas = document
            .get_element_by_id(canvas_id)
            .ok_or_else(|| JsValue::from_str("Canvas não encontrado"))?
            .dyn_into::<HtmlCanvasElement>()?;

        let ctx = canvas
            .get_context("2d")?
            .unwrap()
            .dyn_into::<CanvasRenderingContext2d>()?;

        Ok(App { canvas, ctx })
    }

    pub fn iniciar(&self) -> Result<(), JsValue> {
        let largura = self.canvas.width() as f64;
        let altura = self.canvas.height() as f64;

        // Fundo
        self.ctx.set_fill_style_str("#1a1a2e");
        self.ctx.fill_rect(0.0, 0.0, largura, altura);

        // Título
        self.ctx.set_fill_style_str("#e94560");
        self.ctx.set_font("24px Arial");
        self.ctx.set_text_align("center");
        self.ctx
            .fill_text("Clique para desenhar!", largura / 2.0, 40.0)?;

        // Configurar evento de clique
        let canvas = self.canvas.clone();
        let ctx = self
            .canvas
            .get_context("2d")
            .unwrap()
            .unwrap()
            .dyn_into::<CanvasRenderingContext2d>()
            .unwrap();

        let pontos = Rc::new(RefCell::new(Vec::<Ponto>::new()));
        let pontos_clone = pontos.clone();

        let callback = Closure::wrap(Box::new(move |evento: MouseEvent| {
            let rect = canvas.get_bounding_client_rect();
            let x = evento.client_x() as f64 - rect.left();
            let y = evento.client_y() as f64 - rect.top();

            let cores = ["#e94560", "#0f3460", "#16213e", "#533483", "#e94560"];
            let idx = pontos_clone.borrow().len() % cores.len();

            let ponto = Ponto {
                x,
                y,
                cor: cores[idx].to_string(),
                raio: 5.0 + (pontos_clone.borrow().len() as f64 % 20.0),
            };

            // Desenhar círculo
            ctx.begin_path();
            ctx.arc(ponto.x, ponto.y, ponto.raio, 0.0, std::f64::consts::PI * 2.0)
                .unwrap();
            ctx.set_fill_style_str(&ponto.cor);
            ctx.fill();

            // Conectar ao ponto anterior
            let pontos_ref = pontos_clone.borrow();
            if let Some(anterior) = pontos_ref.last() {
                ctx.begin_path();
                ctx.move_to(anterior.x, anterior.y);
                ctx.line_to(ponto.x, ponto.y);
                ctx.set_stroke_style_str(&ponto.cor);
                ctx.set_line_width(2.0);
                ctx.stroke();
            }
            drop(pontos_ref);

            pontos_clone.borrow_mut().push(ponto);

        }) as Box<dyn FnMut(MouseEvent)>);

        self.canvas.add_event_listener_with_callback(
            "click",
            callback.as_ref().unchecked_ref(),
        )?;
        callback.forget();

        Ok(())
    }

    pub fn limpar(&self) {
        let largura = self.canvas.width() as f64;
        let altura = self.canvas.height() as f64;
        self.ctx.set_fill_style_str("#1a1a2e");
        self.ctx.fill_rect(0.0, 0.0, largura, altura);
    }

    pub fn desenhar_grafico(&self, dados: &[f64]) -> Result<(), JsValue> {
        let largura = self.canvas.width() as f64;
        let altura = self.canvas.height() as f64;
        let margem = 50.0;

        // Limpar
        self.ctx.set_fill_style_str("#1a1a2e");
        self.ctx.fill_rect(0.0, 0.0, largura, altura);

        if dados.is_empty() {
            return Ok(());
        }

        let max_val = dados.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
        let min_val = dados.iter().cloned().fold(f64::INFINITY, f64::min);
        let range = if max_val == min_val { 1.0 } else { max_val - min_val };

        let area_largura = largura - 2.0 * margem;
        let area_altura = altura - 2.0 * margem;
        let passo = area_largura / (dados.len() - 1).max(1) as f64;

        // Eixos
        self.ctx.set_stroke_style_str("#555");
        self.ctx.set_line_width(1.0);
        self.ctx.begin_path();
        self.ctx.move_to(margem, margem);
        self.ctx.line_to(margem, altura - margem);
        self.ctx.line_to(largura - margem, altura - margem);
        self.ctx.stroke();

        // Linha do gráfico
        self.ctx.set_stroke_style_str("#e94560");
        self.ctx.set_line_width(2.0);
        self.ctx.begin_path();

        for (i, &valor) in dados.iter().enumerate() {
            let x = margem + i as f64 * passo;
            let y = altura - margem - ((valor - min_val) / range * area_altura);

            if i == 0 {
                self.ctx.move_to(x, y);
            } else {
                self.ctx.line_to(x, y);
            }
        }
        self.ctx.stroke();

        // Pontos
        self.ctx.set_fill_style_str("#e94560");
        for (i, &valor) in dados.iter().enumerate() {
            let x = margem + i as f64 * passo;
            let y = altura - margem - ((valor - min_val) / range * area_altura);
            self.ctx.begin_path();
            self.ctx.arc(x, y, 4.0, 0.0, std::f64::consts::PI * 2.0)?;
            self.ctx.fill();
        }

        // Labels
        self.ctx.set_fill_style_str("#aaa");
        self.ctx.set_font("12px monospace");
        self.ctx.set_text_align("right");
        self.ctx.fill_text(&format!("{:.1}", max_val), margem - 5.0, margem + 5.0)?;
        self.ctx.fill_text(
            &format!("{:.1}", min_val),
            margem - 5.0,
            altura - margem + 5.0,
        )?;

        Ok(())
    }
}
```

HTML para o exemplo:

```html
<!DOCTYPE html>
<html>
<head>
    <title>Rust WASM Canvas</title>
    <style>
        body {
            background: #0f0f23;
            color: white;
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
        }
        canvas {
            border: 2px solid #333;
            border-radius: 8px;
            cursor: crosshair;
        }
        button {
            margin: 10px;
            padding: 10px 20px;
            background: #e94560;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        button:hover { background: #c73e55; }
    </style>
</head>
<body>
    <h1>Rust + WebAssembly Canvas</h1>
    <canvas id="canvas" width="800" height="500"></canvas>
    <div>
        <button id="btn-limpar">Limpar</button>
        <button id="btn-grafico">Gráfico Demo</button>
    </div>

    <script type="module">
        import init, { App } from './pkg/meu_projeto_wasm.js';

        async function main() {
            await init();

            const app = new App("canvas");
            app.iniciar();

            document.getElementById("btn-limpar").onclick = () => {
                app.limpar();
                app.iniciar();
            };

            document.getElementById("btn-grafico").onclick = () => {
                const dados = Array.from({length: 20}, () => Math.random() * 100);
                app.desenhar_grafico(new Float64Array(dados));
            };
        }

        main();
    </script>
</body>
</html>
```

## Comparação com Alternativas

| Tecnologia | Caso de uso | Destaques |
|------------|-------------|-----------|
| `wasm-bindgen` | Interop Rust-JS | Mais maduro, tipagem rica |
| `stdweb` | Interop Rust-JS | Descontinuado, use wasm-bindgen |
| `Yew` | Framework SPA | Similar ao React, componentes |
| `Leptos` | Framework SPA | Signals, SSR, muito performático |
| `Dioxus` | Framework multiplataforma | Web, desktop, mobile |
| `Trunk` | Build tool | Bundling para projetos WASM |

## Conclusão

A combinação de `wasm-bindgen`, `web-sys` e `wasm-pack` torna Rust uma opção de primeira classe para WebAssembly. Desde processamento de imagens e criptografia até jogos e visualizações de dados, o WASM com Rust entrega performance nativa no navegador com segurança de memória.

O workflow moderno com `wasm-pack` simplifica build, testes e publicação, gerando automaticamente tipos TypeScript e pacotes npm. A interoperabilidade bidirecional entre Rust e JavaScript, com conversão automática de tipos, torna o desenvolvimento surpreendentemente ergonômico.

**Próximos passos:**
- Explore frameworks como Yew ou Leptos para SPAs completas em Rust
- Veja [criterion](/ecossistema/criterion/) para benchmarkar seu código WASM
- Confira [serde](/ecossistema/serde/) para serialização de dados entre Rust e JS
- Aprofunde o contexto em [Rust e WebAssembly em 2026](/blog/rust-webassembly-wasm-2026/) e no tutorial de [WebAssembly com Rust](/tutoriais/webassembly-rust/)
- Se quer transformar isso em carreira, veja [vagas Rust](/vagas/) e [empresas que usam Rust no Brasil](/empresas/)
