Rust para WebAssembly: Guia Prático 2026 | Rust Brasil

Guia prático de Rust para WebAssembly: wasm-pack, wasm-bindgen, web-sys, Trunk, Yew e comparação com JavaScript.

Introdução

WebAssembly (Wasm) revolucionou o que é possível fazer no navegador, e Rust é a linguagem que melhor aproveita essa tecnologia. Enquanto C++ e Go também compilam para Wasm, Rust se destaca por produzir binários menores, sem garbage collector (crítico para Wasm, onde cada kilobyte importa) e com uma interoperabilidade excepcional com JavaScript através do wasm-bindgen.

Em 2026, WebAssembly não é mais apenas para demos técnicas. Empresas como Figma, Cloudflare, Google e Adobe usam Wasm em produção para processamento de imagens, editores colaborativos, engines de busca no cliente e muito mais. Neste artigo, vamos explorar o ecossistema Rust para Wasm, construir um projeto prático e entender quando vale a pena substituir JavaScript por Rust+Wasm.

O Ecossistema Rust para WebAssembly

Ferramentas Essenciais

FerramentaFunção
wasm-packBuild tool que compila Rust para Wasm e gera bindings JS
wasm-bindgenPonte entre Rust e JavaScript — permite chamar APIs JS do Rust
web-sysBindings para todas as Web APIs (DOM, Canvas, Fetch, etc.)
js-sysBindings para objetos nativos do JavaScript
TrunkBuild tool e dev server para aplicações Wasm puras
wasm-optOtimizador de binários Wasm (parte do Binaryen)

Frameworks Frontend em Rust

FrameworkParadigmaDestaque
YewComponent-based (semelhante ao React)Maduro, grande comunidade
LeptosSignals (semelhante ao SolidJS)SSR nativo, fine-grained reactivity
DioxusSemelhante ao React com suporte multi-plataformaDesktop, mobile, web
SycamoreSignals, sem Virtual DOMPerformance máxima

Configuração Base (Cargo.toml)

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

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

[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = [
    "Document", "Element", "HtmlElement",
    "Window", "console", "HtmlCanvasElement",
    "CanvasRenderingContext2d",
] }
js-sys = "0.3"
serde = { version = "1", features = ["derive"] }
serde-wasm-bindgen = "0.6"

[profile.release]
opt-level = "z"     # Otimizar para tamanho
lto = true          # Link-Time Optimization
codegen-units = 1   # Melhor otimização
strip = true        # Remover símbolos de debug

Exemplo Prático: Processador de Imagens no Navegador

Vamos construir um módulo Wasm que aplica filtros em imagens diretamente no navegador, demonstrando a performance superior de Rust para operações computacionais intensivas.

Código Rust (src/lib.rs)

use wasm_bindgen::prelude::*;
use web_sys::console;

/// Aplica um filtro de escala de cinza nos pixels da imagem.
/// Recebe os dados de pixel como um slice mutável (RGBA).
#[wasm_bindgen]
pub fn filtro_escala_cinza(pixels: &mut [u8]) {
    for chunk in pixels.chunks_exact_mut(4) {
        let r = chunk[0] as f32;
        let g = chunk[1] as f32;
        let b = chunk[2] as f32;

        // Fórmula de luminância perceptual (ITU-R BT.709)
        let cinza = (0.2126 * r + 0.7152 * g + 0.0722 * b) as u8;

        chunk[0] = cinza;
        chunk[1] = cinza;
        chunk[2] = cinza;
        // chunk[3] (alpha) permanece inalterado
    }
}

/// Aplica um filtro de desfoque (blur) simples usando média de vizinhos.
#[wasm_bindgen]
pub fn filtro_blur(pixels: &mut [u8], largura: u32, altura: u32, raio: u32) {
    let len = pixels.len();
    let mut saida = vec![0u8; len];
    let largura = largura as usize;
    let altura = altura as usize;
    let raio = raio as usize;

    for y in 0..altura {
        for x in 0..largura {
            let mut r_total: u64 = 0;
            let mut g_total: u64 = 0;
            let mut b_total: u64 = 0;
            let mut contagem: u64 = 0;

            let y_min = y.saturating_sub(raio);
            let y_max = (y + raio + 1).min(altura);
            let x_min = x.saturating_sub(raio);
            let x_max = (x + raio + 1).min(largura);

            for ny in y_min..y_max {
                for nx in x_min..x_max {
                    let idx = (ny * largura + nx) * 4;
                    r_total += pixels[idx] as u64;
                    g_total += pixels[idx + 1] as u64;
                    b_total += pixels[idx + 2] as u64;
                    contagem += 1;
                }
            }

            let idx = (y * largura + x) * 4;
            saida[idx] = (r_total / contagem) as u8;
            saida[idx + 1] = (g_total / contagem) as u8;
            saida[idx + 2] = (b_total / contagem) as u8;
            saida[idx + 3] = pixels[idx + 3]; // preserva alpha
        }
    }

    pixels.copy_from_slice(&saida);
}

/// Ajusta o brilho da imagem. Valores positivos clareiam, negativos escurecem.
#[wasm_bindgen]
pub fn ajustar_brilho(pixels: &mut [u8], fator: f32) {
    for chunk in pixels.chunks_exact_mut(4) {
        chunk[0] = ((chunk[0] as f32 * fator).clamp(0.0, 255.0)) as u8;
        chunk[1] = ((chunk[1] as f32 * fator).clamp(0.0, 255.0)) as u8;
        chunk[2] = ((chunk[2] as f32 * fator).clamp(0.0, 255.0)) as u8;
    }
}

/// Aplica um filtro sépia (tom envelhecido).
#[wasm_bindgen]
pub fn filtro_sepia(pixels: &mut [u8]) {
    for chunk in pixels.chunks_exact_mut(4) {
        let r = chunk[0] as f32;
        let g = chunk[1] as f32;
        let b = chunk[2] as f32;

        chunk[0] = ((r * 0.393 + g * 0.769 + b * 0.189).min(255.0)) as u8;
        chunk[1] = ((r * 0.349 + g * 0.686 + b * 0.168).min(255.0)) as u8;
        chunk[2] = ((r * 0.272 + g * 0.534 + b * 0.131).min(255.0)) as u8;
    }
}

/// Retorna informações sobre o módulo Wasm.
#[wasm_bindgen]
pub fn info() -> String {
    format!(
        "Processador de Imagens Wasm v{} — compilado com Rust",
        env!("CARGO_PKG_VERSION")
    )
}

#[wasm_bindgen(start)]
pub fn inicializar() {
    console::log_1(&"Módulo Wasm de processamento de imagens carregado!".into());
}

Integração com JavaScript

import init, {
    filtro_escala_cinza,
    filtro_blur,
    ajustar_brilho,
    filtro_sepia,
    info
} from './pkg/meu_projeto_wasm.js';

async function main() {
    await init();
    console.log(info());

    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const img = document.getElementById('imagem-fonte');

    canvas.width = img.naturalWidth;
    canvas.height = img.naturalHeight;
    ctx.drawImage(img, 0, 0);

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    // Benchmark: Rust (Wasm) vs JavaScript puro
    console.time('Wasm - Escala de cinza');
    filtro_escala_cinza(imageData.data);
    console.timeEnd('Wasm - Escala de cinza');

    ctx.putImageData(imageData, 0, 0);
}

main();

Build e Execução

# Instalar wasm-pack
cargo install wasm-pack

# Compilar para navegador
wasm-pack build --target web --release

# O resultado estará em ./pkg/
# - meu_projeto_wasm_bg.wasm (binário otimizado)
# - meu_projeto_wasm.js (bindings JavaScript)
# - meu_projeto_wasm.d.ts (tipos TypeScript)

Aplicação Full-Stack com Yew

Para quem quer construir uma SPA completa em Rust, Yew oferece uma experiência semelhante ao React:

[package]
name = "meu-app-yew"
version = "0.1.0"
edition = "2021"

[dependencies]
yew = { version = "0.21", features = ["csr"] }
gloo-net = "0.6"
serde = { version = "1", features = ["derive"] }
wasm-bindgen-futures = "0.4"
use gloo_net::http::Request;
use serde::Deserialize;
use yew::prelude::*;

#[derive(Clone, Debug, Deserialize, PartialEq)]
struct Repositorio {
    name: String,
    description: Option<String>,
    stargazers_count: u32,
    html_url: String,
}

#[function_component(App)]
fn app() -> Html {
    let repos = use_state(|| Vec::<Repositorio>::new());
    let carregando = use_state(|| true);

    {
        let repos = repos.clone();
        let carregando = carregando.clone();
        use_effect_with((), move |_| {
            wasm_bindgen_futures::spawn_local(async move {
                let resposta: Vec<Repositorio> = Request::get(
                    "https://api.github.com/users/nickel-org/repos?sort=stars&per_page=5"
                )
                .send()
                .await
                .unwrap()
                .json()
                .await
                .unwrap();

                repos.set(resposta);
                carregando.set(false);
            });
        });
    }

    html! {
        <div class="app">
            <h1>{"Repositórios Rust Populares"}</h1>
            if *carregando {
                <p>{"Carregando..."}</p>
            } else {
                <ul>
                    { for repos.iter().map(|repo| html! {
                        <li key={repo.name.clone()}>
                            <a href={repo.html_url.clone()} target="_blank">
                                <strong>{ &repo.name }</strong>
                            </a>
                            {" — "}
                            { repo.description.as_deref().unwrap_or("Sem descrição") }
                            <span class="stars">
                                { format!(" ★ {}", repo.stargazers_count) }
                            </span>
                        </li>
                    })}
                </ul>
            }
        </div>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

Build com Trunk

# Instalar Trunk
cargo install trunk

# Criar index.html na raiz do projeto
# Rodar servidor de desenvolvimento
trunk serve

# Build para produção
trunk build --release

Performance: Rust+Wasm vs JavaScript

Em operações computacionais intensivas, Rust compilado para Wasm costuma ser 2-10x mais rápido que JavaScript equivalente:

OperaçãoJavaScriptRust (Wasm)Speedup
Filtro de imagem (1920x1080)120ms15ms8x
Parsing JSON grande (10MB)250ms80ms3x
Cálculo de hash SHA-25645ms8ms5.6x
Simulação física (1000 objetos)16ms/frame3ms/frame5.3x
Compressão de texto (1MB)200ms30ms6.7x

A vantagem diminui para operações simples de DOM ou I/O, onde o overhead de atravessar a ponte Wasm/JS anula os ganhos. A regra geral: use Wasm para computação pesada, mantenha JS para manipulação de DOM e I/O.

Empresas Usando Rust + WebAssembly

  • Figma: Motor de renderização do editor de design é Rust compilado para Wasm, processando milhões de operações gráficas no navegador
  • Cloudflare Workers: Runtime serverless que executa Wasm no edge, com suporte de primeira classe para Rust
  • Google: O Google Earth usa Wasm para renderização 3D no navegador
  • Adobe: Photoshop para web utiliza Wasm para filtros e processamento de imagem
  • Amazon Prime Video: Componentes de streaming e decodificação
  • 1Password: Lógica de criptografia no navegador via Wasm

Como Começar

  1. Instale as ferramentas: Certifique-se de ter o Rust instalado (guia de instalação) e adicione o target wasm: rustup target add wasm32-unknown-unknown
  2. Aprenda o básico de Rust: Tutorial de primeiros passos
  3. Siga o tutorial de WebAssembly: Tutorial completo de WebAssembly com Rust
  4. Entenda serialização: Receita de JSON — essencial para trocar dados com JS
  5. Explore frameworks: Comece com Yew ou Leptos para uma SPA completa

Conclusão

Rust e WebAssembly formam uma combinação poderosa que está redefinindo o que é possível no navegador. Para tarefas computacionais intensivas — processamento de imagem, criptografia, simulações, parsers — a performance de Rust compilado para Wasm é imbatível. Com ferramentas como wasm-pack e frameworks como Yew e Leptos, a experiência de desenvolvimento melhorou drasticamente, tornando viável escrever aplicações web completas em Rust.


Veja Também