WebAssembly deixou de ser apenas uma forma de rodar código no navegador. Em 2026, a conversa mais interessante para quem trabalha com Zig é outra: usar WASI e Component Model para distribuir plugins, automações, filtros, extensões e pequenas funções de backend sem empacotar um runtime pesado junto.
Zig combina naturalmente com esse cenário. A linguagem já trata cross-compilation como parte central da experiência, gera binários pequenos e não depende de garbage collector. Isso faz diferença quando você quer entregar um módulo .wasm que roda em um host controlado, com permissões explícitas e fronteiras bem definidas. O objetivo não é substituir todo backend por WebAssembly. O objetivo é criar pontos de extensão seguros e portáveis.
Este guia explica onde Zig encaixa com WASI e Component Model, quando usar wasm32-wasi, como desenhar contratos de plugin, quais limites ainda existem e como levar a ideia para produção sem transformar o projeto em laboratório. Para a base, leia também Zig e WebAssembly em 2026, cross-compilation em Zig, supply chain de dependências e configuração segura com segredos e variáveis de ambiente.
O que muda com WASI
WebAssembly puro define um formato de execução isolado, mas não define por si só como abrir arquivo, ler relógio, gerar número aleatório, escrever em stdout ou fazer I/O de rede. WASI, a WebAssembly System Interface, existe para preencher essa lacuna com uma interface de sistema operacional mais portável e mais restrita.
Em vez de dar ao módulo acesso livre ao sistema, o host decide quais capacidades estão disponíveis. Um plugin pode receber permissão para ler um diretório específico, escrever em stdout e acessar relógio. Outro pode não ter filesystem nenhum. Esse modelo é interessante para extensões carregadas de terceiros, pipelines de dados, filtros de conteúdo, regras de negócio customizadas e ferramentas internas que precisam ser distribuídas com menos atrito.
Com Zig, o primeiro alvo prático costuma ser:
zig build-exe src/main.zig -target wasm32-wasi -O ReleaseSmall
Esse comando não resolve arquitetura de produto sozinho. Ele apenas gera um módulo para um ambiente WASI. A parte importante vem depois: definir o contrato entre host e plugin, controlar permissões, versionar entradas e saídas, testar em um runtime real e observar falhas.
Onde o Component Model entra
O Component Model tenta resolver um problema recorrente em WebAssembly: como módulos escritos em linguagens diferentes conversam por tipos de alto nível, sem cada projeto inventar seu próprio ABI manual. Em vez de exportar apenas funções com inteiros e ponteiros, você descreve interfaces com tipos como string, list, record, variant e result.
Na prática, isso permite imaginar um plugin com contrato assim:
package zigbrasil:filters;
interface text-filter {
record input {
title: string,
body: string,
locale: string,
}
record output {
body: string,
warnings: list<string>,
}
filter: func(input: input) -> result<output, string>;
}
O host carrega um componente que implementa text-filter, passa um documento e recebe o resultado. A vantagem é que o contrato fica explícito e versionável. A desvantagem é que a ferramenta ainda está amadurecendo, e nem todo fluxo em Zig é tão direto quanto compilar um executável nativo.
Por isso, uma estratégia realista em 2026 é separar dois níveis:
- WASI clássico para módulos simples: CLI, filtros por stdin/stdout, tarefas batch, validadores e pequenos workers.
- Component Model para plugins com contrato público: extensões carregadas por produto, marketplace interno, sandbox multi-tenant ou integração entre linguagens.
Não comece pelo Component Model se um contrato por JSON em stdin/stdout resolve. Comece por ele quando o custo de tipar e versionar a interface for menor do que o custo de manter convenções soltas.
Casos em que Zig + WASI faz sentido
Zig não vira melhor escolha só porque o alvo é WebAssembly. Ele se destaca quando você precisa de controle de memória, binário pequeno, performance previsível e pouca dependência externa.
Bons casos:
- filtros de conteúdo executados dentro de uma aplicação maior;
- plugins de produto que precisam rodar com permissões limitadas;
- validadores de configuração ou política distribuídos como artefato único;
- transforms de dados em pipeline, especialmente CSV, JSONL, logs e payloads binários;
- extensões de edge computing onde startup e footprint importam;
- funções internas em que o host é Node, Go, Rust ou Python, mas o trecho quente pode ser Zig;
- ferramentas de build ou release que precisam rodar igual em Linux, macOS e CI.
Casos menos bons:
- aplicação web CRUD convencional;
- integração que depende de SDK grande e mutável;
- código que precisa de muitas bibliotecas nativas ainda não preparadas para WASI;
- produto onde a equipe não tem tempo para cuidar de runtime, contrato e observabilidade;
- lógica que precisa de acesso amplo a rede, filesystem e processos externos.
WebAssembly é uma fronteira. Ele fica forte quando a fronteira é pequena. Se o módulo precisa de acesso a tudo, talvez você só criou um container pior.
Arquitetura simples: plugin por stdin/stdout
Antes de investir em Component Model, dá para entregar muito valor com um contrato simples: o host chama o módulo ou adaptador, envia JSON no stdin e lê JSON no stdout. Esse desenho é compatível com a mentalidade de servidores MCP em Zig e com ferramentas de automação pequenas.
Exemplo de entrada:
{
"version": 1,
"operation": "normalize_title",
"input": {
"title": " Zig + WASI: guia prático "
}
}
Exemplo de saída:
{
"ok": true,
"output": {
"title": "Zig + WASI: guia prático",
"slug": "zig-wasi-guia-pratico"
},
"warnings": []
}
Em Zig, a regra é manter o envelope estável e validar tudo na borda. Use limite de tamanho de entrada, arena por requisição e erros explícitos. Reaproveite os padrões de std.json e da receita de parsear JSON em Zig.
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
const stdin = std.io.getStdIn().reader();
const stdout = std.io.getStdOut().writer();
const payload = try stdin.readAllAlloc(arena.allocator(), 256 * 1024);
if (payload.len == 0) {
try stdout.writeAll("{\"ok\":false,\"error\":\"empty_input\"}\n");
return;
}
var parsed = try std.json.parseFromSlice(
std.json.Value,
arena.allocator(),
payload,
.{},
);
defer parsed.deinit();
// Valide version, operation e input antes de executar regra real.
try stdout.writeAll("{\"ok\":true,\"output\":{},\"warnings\":[]}\n");
}
O exemplo é propositalmente pequeno. Em produção, crie tipos para entrada e saída, trate erro de parse, escreva testes e rejeite campos perigosos. O ponto é que um plugin WASI não precisa começar com rede, servidor HTTP ou framework.
Permissões: o host manda
A segurança de WASI não vem do arquivo .wasm sozinho. Ela vem da combinação entre sandbox e host. O host decide quais diretórios são pré-abertos, quais variáveis de ambiente existem, quais clocks estão disponíveis e se rede é permitida.
Uma política segura para plugins começa assim:
- sem acesso a secrets por padrão;
- sem filesystem por padrão;
- diretórios pré-abertos somente quando necessário;
- entrada e saída por contrato, não por caminho arbitrário;
- limite de CPU/tempo no runtime;
- limite de memória;
- logs sem payload sensível;
- versão do plugin registrada junto do resultado.
Se um plugin precisa processar arquivos, prefira receber um diretório de trabalho temporário com apenas os arquivos necessários. Não passe /home, /var, checkout inteiro do repositório ou pasta com credenciais. Essa disciplina é a mesma de qualquer ferramenta de automação segura: capacidade mínima, tempo limitado e resultado auditável.
Contratos e versionamento
O erro comum é tratar o módulo WASM como detalhe de implementação. Para plugins, o contrato é produto. Versione o envelope de entrada, documente campos obrigatórios e pense no que acontece quando o host é atualizado antes do plugin ou o plugin antes do host.
Um contrato mínimo deve responder:
| Pergunta | Decisão recomendada |
|---|---|
| Como o plugin declara versão? | Campo version e metadados no artefato |
| Como falhas são retornadas? | result no Component Model ou { ok: false, error } no JSON |
| Erro é retryable? | Campo explícito, não inferência por texto |
| O plugin pode emitir warnings? | Sim, lista estruturada |
| O host pode ignorar campos desconhecidos? | Sim para compatibilidade, mas logue |
| Como quebrar compatibilidade? | Nova versão de interface, não gambiarra |
Se você usa Component Model, WIT vira a fonte do contrato. Se usa JSON, mantenha um schema ou pelo menos testes de compatibilidade. Para APIs HTTP em volta do host, o mesmo raciocínio aparece no guia de OpenAPI em Zig.
Build e release
Para um módulo simples, o build pode começar com zig build-exe. Em projetos reais, coloque o alvo WASI no build.zig, gere artefato nomeado e publique junto com checksum.
Checklist de release:
- build reproduzível no CI;
ReleaseSmallouReleaseSafeescolhido conscientemente;- testes unitários nativos;
- teste de execução no runtime WASI usado em produção;
- limite de tamanho do artefato;
- checksum publicado;
- changelog da interface;
- rollback para versão anterior.
Não pule o teste no runtime real. Compilar para wasm32-wasi e executar em Wasmtime, Wasmer, Node, Spin ou outro host não são exatamente a mesma validação. O contrato com o runtime faz parte do produto.
Observabilidade sem vazar dados
Plugins falham. Podem receber entrada inválida, estourar limite de memória, demorar demais, bater em permissão negada ou retornar erro de regra. O host precisa transformar isso em sinal útil.
Métricas úteis:
- duração por plugin e operação;
- contagem de sucesso, erro de validação e erro interno;
- timeout;
- memória máxima quando o runtime expõe;
- versão do plugin;
- tamanho de entrada e saída em faixas, não payload bruto;
- quantidade de warnings.
Logs devem carregar plugin_name, plugin_version, operation, request_id e classe de erro. Não grave conteúdo completo de documentos, tokens, headers, emails ou payloads de cliente. Se a execução participa de um serviço maior, conecte com OpenTelemetry em Zig no host, mesmo que o plugin em si seja pequeno.
WASI não substitui containers
É tentador vender WebAssembly como “containers menores”. Às vezes, sim. Mas a comparação honesta é mais específica.
WASI é bom para executar uma unidade pequena, com contrato estreito, permissões explícitas e startup rápido. Container é melhor quando você precisa de processo completo, filesystem amplo, bibliotecas nativas complexas, várias portas, sidecars e compatibilidade operacional tradicional.
Use WASI para plugins e funções. Use container para serviços. Quando o módulo começa a precisar de banco direto, rede ampla, cache, fila, configuração dinâmica e múltiplos workers, talvez ele deva virar serviço normal. Zig continua útil dos dois lados.
Um caminho de adoção seguro
Se você quer testar Zig + WASI em um produto real, faça em camadas:
- escolha uma regra pequena e sem dado sensível;
- defina entrada e saída JSON versionadas;
- compile para
wasm32-wasi; - rode em um host local com permissões mínimas;
- adicione timeout e limite de memória;
- publique checksum do artefato;
- meça latência e falhas;
- só depois avalie Component Model.
Esse caminho evita o erro de começar pela ferramenta mais nova antes de provar o valor. Se a primeira regra já reduz deploy, melhora isolamento ou facilita extensão por terceiros, você ganhou. Se não muda nada, mantenha um binário nativo ou uma biblioteca normal.
Conclusão
Zig, WASI e Component Model formam uma combinação promissora para plugins WebAssembly em 2026. A força está em módulos pequenos, previsíveis, portáveis e executados com capacidade mínima. Zig ajuda porque compila bem para alvos diferentes, não carrega runtime pesado e obriga você a modelar memória e erros de forma explícita.
A decisão prática é simples: use wasm32-wasi quando precisar distribuir uma função isolada com sandbox e baixo atrito. Use Component Model quando a interface virar contrato público entre host e plugins. E não use WebAssembly como desculpa para esconder um serviço complexo dentro de uma caixa menor. A fronteira tem que ser pequena, testada e observável.