Zig e WebAssembly — Ferramentas, Compilação e Runtime WASM

Zig e WebAssembly — Ferramentas, Compilação e Runtime WASM

O Zig é uma das melhores linguagens para desenvolvimento WebAssembly. Com suporte nativo a WASM como target de compilação, binários extremamente pequenos e controle total sobre alocação de memória, o Zig produz módulos WASM que frequentemente são menores e mais rápidos que equivalentes em C, C++ ou Rust.

Por Que Zig para WebAssembly

O Zig possui características que o tornam ideal para WASM:

  • Binários minúsculos: Um hello world WASM em Zig pode ter menos de 1 KB
  • Sem runtime: Nenhum código de inicialização oculto no binário
  • Controle de memória: Sem GC, ideal para o modelo de memória linear do WASM
  • Compilação nativa: wasm32-freestanding e wasm32-wasi são targets de primeira classe
  • Interop C: Use qualquer biblioteca C compilável para WASM

Compilando para WebAssembly

Target wasm32-freestanding (Browser)

// src/lib.zig
export fn somar(a: i32, b: i32) i32 {
    return a + b;
}

export fn fatorial(n: u32) u64 {
    if (n <= 1) return 1;
    var resultado: u64 = 1;
    var i: u32 = 2;
    while (i <= n) : (i += 1) {
        resultado *= i;
    }
    return resultado;
}

export fn fibonacci(n: u32) u64 {
    if (n <= 1) return n;
    var a: u64 = 0;
    var b: u64 = 1;
    var i: u32 = 2;
    while (i <= n) : (i += 1) {
        const temp = a + b;
        a = b;
        b = temp;
    }
    return b;
}
# Compilar para WASM
zig build-lib src/lib.zig -target wasm32-freestanding -dynamic -O ReleaseSmall

build.zig para WASM

const std = @import("std");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});

    const lib = b.addSharedLibrary(.{
        .name = "app",
        .root_source_file = b.path("src/lib.zig"),
        .target = b.resolveTargetQuery(.{
            .cpu_arch = .wasm32,
            .os_tag = .freestanding,
        }),
        .optimize = optimize,
    });

    // Exportar tabela de memória
    lib.export_memory = true;
    lib.initial_memory = 65536; // 1 página (64KB)

    b.installArtifact(lib);
}

Usando no Browser

<!DOCTYPE html>
<html>
<head><title>Zig WASM Demo</title></head>
<body>
<script>
async function init() {
    const response = await fetch('app.wasm');
    const bytes = await response.arrayBuffer();
    const { instance } = await WebAssembly.instantiate(bytes, {
        env: {
            // Funções importadas do JavaScript
            log: (valor) => console.log('Zig diz:', valor),
        }
    });

    // Chamar funções exportadas do Zig
    console.log('2 + 3 =', instance.exports.somar(2, 3));
    console.log('10! =', instance.exports.fatorial(10));
    console.log('fib(20) =', instance.exports.fibonacci(20));
}

init();
</script>
</body>
</html>

WASI — WebAssembly System Interface

Para aplicações que precisam de acesso ao sistema de arquivos e rede:

// src/main.zig - WASI
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Olá do Zig WASI!\n", .{});

    // Acessar argumentos
    var args = std.process.args();
    while (args.next()) |arg| {
        try stdout.print("Arg: {s}\n", .{arg});
    }

    // Acessar variáveis de ambiente
    const path = std.posix.getenv("PATH") orelse "não definido";
    try stdout.print("PATH: {s}\n", .{path});
}
# Compilar para WASI
zig build-exe src/main.zig -target wasm32-wasi -O ReleaseSmall

# Executar com wasmtime
wasmtime main.wasm

# Executar com wasmer
wasmer main.wasm

Interação Avançada com JavaScript

Passando Strings

// Zig: exportar string para JavaScript
var buffer: [1024]u8 = undefined;

export fn obter_saudacao(nome_ptr: [*]const u8, nome_len: usize) usize {
    const nome = nome_ptr[0..nome_len];
    const resultado = std.fmt.bufPrint(&buffer, "Olá, {s}! Bem-vindo ao Zig WASM.", .{nome}) catch return 0;
    return resultado.len;
}

export fn obter_buffer_ptr() [*]const u8 {
    return &buffer;
}
// JavaScript: trocar strings com Zig
const encoder = new TextEncoder();
const decoder = new TextDecoder();

function saudar(nome) {
    const nomeBytes = encoder.encode(nome);
    const memory = instance.exports.memory;
    const memView = new Uint8Array(memory.buffer);

    // Copiar nome para memória WASM
    const nomePtr = instance.exports.__heap_base;
    memView.set(nomeBytes, nomePtr);

    // Chamar função Zig
    const resultLen = instance.exports.obter_saudacao(nomePtr, nomeBytes.length);
    const bufPtr = instance.exports.obter_buffer_ptr();

    // Ler resultado
    const resultado = decoder.decode(memView.slice(bufPtr, bufPtr + resultLen));
    console.log(resultado);
}

Canvas e Gráficos

// Renderização em canvas via WASM
const LARGURA: u32 = 800;
const ALTURA: u32 = 600;
var framebuffer: [LARGURA * ALTURA * 4]u8 = undefined;

export fn obter_framebuffer() [*]u8 {
    return &framebuffer;
}

export fn obter_largura() u32 {
    return LARGURA;
}

export fn obter_altura() u32 {
    return ALTURA;
}

export fn renderizar(tempo: f64) void {
    for (0..ALTURA) |y| {
        for (0..LARGURA) |x| {
            const idx = (y * LARGURA + x) * 4;
            const fx = @as(f32, @floatFromInt(x)) / @as(f32, @floatFromInt(LARGURA));
            const fy = @as(f32, @floatFromInt(y)) / @as(f32, @floatFromInt(ALTURA));
            _ = tempo;

            framebuffer[idx + 0] = @intFromFloat(fx * 255); // R
            framebuffer[idx + 1] = @intFromFloat(fy * 255); // G
            framebuffer[idx + 2] = 128;                       // B
            framebuffer[idx + 3] = 255;                       // A
        }
    }
}

Otimização de Tamanho

Técnicas para minimizar o binário WASM:

# Usar ReleaseSmall
zig build -Doptimize=ReleaseSmall -Dtarget=wasm32-freestanding

# Strip de seções desnecessárias
wasm-strip output.wasm

# Otimizar com wasm-opt (Binaryen)
wasm-opt -O3 -o output-opt.wasm output.wasm
OtimizaçãoTamanho típico
Debug~50 KB
ReleaseSafe~20 KB
ReleaseFast~15 KB
ReleaseSmall~5 KB
ReleaseSmall + strip~2 KB
ReleaseSmall + wasm-opt~1.5 KB

Edge Computing com WASM

O Zig é usado por empresas como Cloudflare para Workers WASM em edge computing, aproveitando o startup rápido e tamanho mínimo dos binários.

Boas Práticas

  1. Use ReleaseSmall para binários web — cada byte conta
  2. Minimize imports — reduza dependências do host
  3. Pré-aloque memória — evite crescimento dinâmico da memória WASM
  4. Teste em múltiplos runtimes — wasmtime, wasmer, browser
  5. Profile com ferramentas WASM — use wasm-opt analyze para identificar gargalos

Próximos Passos

Explore as bibliotecas gráficas para renderização WASM, os frameworks web para backends WASI, e o case da Cloudflare para uso em produção. Consulte nossos tutoriais para projetos WASM práticos.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.