Se você trabalha com JavaScript e já ouviu falar do Bun, provavelmente se perguntou: o que é essa linguagem usada para construí-lo? A resposta é zig lang, uma linguagem de programação de sistemas que está ganhando tração no ecossistema JavaScript. A linguagem Zig combina a performance do C com uma experiência de desenvolvimento moderna, e neste guia vamos explorar como seus conhecimentos de JavaScript se traduzem para Zig.
Por Que Desenvolvedores JS Deveriam Conhecer Zig
O ecossistema JavaScript está cada vez mais conectado ao Zig. Veja por quê:
- Bun é escrito em Zig: O runtime JavaScript mais rápido do mercado foi construído com Zig. Entender Zig é entender o motor por trás do Bun.
- Performance nativa: Quando seu servidor Node.js não aguenta a carga, reescrever partes críticas em Zig pode resolver o gargalo.
- WebAssembly: Zig compila diretamente para WASM, permitindo executar código de alta performance no navegador.
- Node.js addons: Crie extensões nativas para Node.js com Zig, substituindo C++ como linguagem padrão para addons.
- Entendimento profundo: Compreender como o V8 e o event loop funcionam por baixo fica muito mais fácil quando você conhece programação de sistemas.
Diferenças Fundamentais: JavaScript vs Zig
| Aspecto | JavaScript | Zig |
|---|---|---|
| Execução | V8/SpiderMonkey (JIT) | Compilado (LLVM) |
| Tipagem | Dinâmica, fraca | Estática, forte |
| Memória | Garbage Collector | Gerenciamento manual |
| Concorrência | Event loop (single thread) | Threads reais |
| Pacotes | npm/yarn/pnpm | zig build + packages |
| Null | null e undefined | null (optional explícito) |
| Erros | throw/try/catch | Error unions |
| OOP | Prototypes/Classes | Structs |
Variáveis: let/const vs var/const
JavaScript:
let nome = "Maria";
const idade = 28;
let contador = 0;
contador += 1;
// Tipo é inferido e pode mudar
let valor = 42;
valor = "quarenta e dois"; // OK em JS
Zig:
var nome: []const u8 = "Maria";
const idade: u32 = 28;
var contador: u32 = 0;
contador += 1;
// Tipo é fixo após declaração
var valor: i32 = 42;
// valor = "quarenta e dois"; // ERRO de compilação!
Em Zig, const é verdadeiramente imutável (diferente do const de JS que permite mutação de objetos). A variável var permite reatribuição, mas o tipo nunca muda. Essa distinção é especialmente importante quando se trabalha com gerenciamento de memória em Zig, onde controlar mutabilidade é essencial.
Zig também tem inferência de tipos quando o valor inicial é claro:
const nome = "Maria"; // tipo inferido: *const [5:0]u8
const idade = @as(u32, 28); // tipo explicitado via @as
Funções
JavaScript:
function somar(a, b) {
return a + b;
}
const multiplicar = (a, b) => a * b;
// Função com valor padrão
function cumprimentar(nome = "mundo") {
return `Olá, ${nome}!`;
}
Zig:
const std = @import("std");
fn somar(a: i32, b: i32) i32 {
return a + b;
}
// Não existe arrow function, mas funções podem ser constantes
const multiplicar = struct {
fn call(a: i32, b: i32) i32 {
return a * b;
}
}.call;
pub fn main() void {
const resultado = somar(3, 5);
std.debug.print("Soma: {}\n", .{resultado});
const produto = multiplicar(4, 6);
std.debug.print("Produto: {}\n", .{produto});
}
Diferenças importantes:
- Parâmetros e retorno sempre têm tipos explícitos.
- Não existem arrow functions, mas Zig tem formas de passar funções como valores.
- Não existem valores padrão para parâmetros (mas há técnicas com structs de opções).
Closures e Callbacks
JavaScript depende muito de closures e callbacks. Zig tem uma abordagem diferente.
JavaScript:
function criarContador() {
let count = 0;
return {
incrementar: () => ++count,
valor: () => count
};
}
const contador = criarContador();
contador.incrementar();
console.log(contador.valor()); // 1
// Callback
[1, 2, 3].map(x => x * 2); // [2, 4, 6]
Zig:
const std = @import("std");
const Contador = struct {
count: i32 = 0,
pub fn incrementar(self: *Contador) void {
self.count += 1;
}
pub fn valor(self: Contador) i32 {
return self.count;
}
};
pub fn main() void {
var contador = Contador{};
contador.incrementar();
std.debug.print("Valor: {}\n", .{contador.valor()});
}
Zig não tem closures como JavaScript. Em vez disso, usamos structs com estado. Para operações como map, Zig usa loops explícitos:
const std = @import("std");
pub fn main() void {
const entrada = [_]i32{ 1, 2, 3 };
var saida: [3]i32 = undefined;
for (entrada, 0..) |valor, i| {
saida[i] = valor * 2;
}
// saida agora é { 2, 4, 6 }
for (saida) |v| {
std.debug.print("{} ", .{v});
}
}
Async/Await: Event Loop vs Threads
Uma das maiores diferenças conceituais está na concorrência.
JavaScript:
async function buscarDados(url) {
try {
const response = await fetch(url);
const dados = await response.json();
return dados;
} catch (erro) {
console.error("Falha:", erro);
}
}
// Promise.all para paralelismo
const [usuarios, posts] = await Promise.all([
buscarDados("/api/usuarios"),
buscarDados("/api/posts")
]);
Em Zig, a concorrência é baseada em threads reais do sistema operacional:
const std = @import("std");
fn tarefaPesada(id: u32) void {
std.debug.print("Thread {} iniciada\n", .{id});
// Simular trabalho pesado
std.time.sleep(1_000_000_000); // 1 segundo
std.debug.print("Thread {} finalizada\n", .{id});
}
pub fn main() !void {
var threads: [4]std.Thread = undefined;
// Criar 4 threads
for (0..4) |i| {
threads[i] = try std.Thread.spawn(.{}, tarefaPesada, .{@as(u32, @intCast(i))});
}
// Aguardar todas finalizarem (equivalente conceitual ao Promise.all)
for (&threads) |*t| {
t.join();
}
std.debug.print("Todas as threads finalizaram\n", .{});
}
Em JavaScript, o event loop gerencia I/O assíncrono em uma única thread. Em Zig, você tem controle direto sobre threads do sistema operacional, podendo executar código verdadeiramente paralelo em múltiplos núcleos.
Tratamento de Erros
JavaScript:
function dividir(a, b) {
if (b === 0) throw new Error("Divisão por zero");
return a / b;
}
try {
const resultado = dividir(10, 0);
console.log(resultado);
} catch (e) {
console.error(e.message);
}
Zig:
const std = @import("std");
const MathError = error{
DivisaoPorZero,
};
fn dividir(a: f64, b: f64) MathError!f64 {
if (b == 0.0) return MathError.DivisaoPorZero;
return a / b;
}
pub fn main() void {
const resultado = dividir(10.0, 0.0) catch |err| {
std.debug.print("Erro: {}\n", .{err});
return;
};
std.debug.print("Resultado: {d}\n", .{resultado});
}
A diferença fundamental: em JavaScript, qualquer função pode lançar uma exceção a qualquer momento, e não há como saber pelo tipo. Em Zig, se uma função pode falhar, o tipo de retorno deixa isso explícito (MathError!f64). O compilador garante que todo erro seja tratado. Para se aprofundar nesse tema, veja o guia completo sobre tratamento de erros em Zig.
O operador try em Zig propaga erros automaticamente, similar a não usar try/catch em JavaScript e deixar a exceção subir:
fn processarDados() !void {
const valor = try dividir(10.0, 2.0); // Se falhar, propaga o erro
std.debug.print("Valor: {d}\n", .{valor});
}
Objetos e JSON: Prototypes vs Structs
JavaScript:
const usuario = {
nome: "Carlos",
idade: 30,
ativo: true
};
const json = JSON.stringify(usuario);
const parsed = JSON.parse(json);
Zig:
const std = @import("std");
const Usuario = struct {
nome: []const u8,
idade: u32,
ativo: bool,
};
pub fn main() !void {
const usuario = Usuario{
.nome = "Carlos",
.idade = 30,
.ativo = true,
};
// Serializar para JSON
var buf: [256]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
try std.json.stringify(usuario, .{}, stream.writer());
const json_str = stream.getWritten();
std.debug.print("JSON: {s}\n", .{json_str});
}
Em JavaScript, objetos são dinâmicos e podem ter qualquer propriedade adicionada em tempo de execução. Em Zig, structs têm campos fixos definidos em tempo de compilação.
Como o Bun Usa Zig
O Bun, criado por Jarred Sumner, é um runtime JavaScript que compete com Node.js e Deno. Ele escolheu Zig em vez de C++ ou Rust pelos seguintes motivos:
- Interoperabilidade com C: Zig pode chamar código C diretamente, sem FFI overhead. Isso foi essencial para integrar com o JavaScriptCore (motor JS do Safari/WebKit).
- Performance previsível: Sem garbage collector, o Bun pode garantir latências baixas e consistentes.
- Alocadores customizados: Zig permite usar diferentes estratégias de alocação de memória, o que o Bun explora para otimizar diferentes tipos de operação.
- Compilação rápida: O tempo de compilação de Zig é significativamente menor que C++, acelerando o desenvolvimento.
Exemplo conceitual de como o Bun usa Zig para processar requisições HTTP:
const std = @import("std");
const net = std.net;
pub fn main() !void {
// Servidor TCP básico (conceito simplificado do que o Bun faz)
var server = try net.StreamServer.init(.{});
defer server.deinit();
try server.listen(net.Address.parseIp("0.0.0.0", 3000) catch unreachable);
std.debug.print("Servidor rodando na porta 3000\n", .{});
while (true) {
if (server.accept()) |conn| {
defer conn.stream.close();
// Processar requisição...
_ = try conn.stream.write("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK");
} else |_| {
continue;
}
}
}
WebAssembly: Usando Zig no Navegador
Uma das formas mais naturais de usar Zig como desenvolvedor JavaScript é compilando para WebAssembly.
// math.zig - será compilado para WASM
export fn fibonacci(n: u32) u32 {
if (n <= 1) return n;
var a: u32 = 0;
var b: u32 = 1;
var i: u32 = 2;
while (i <= n) : (i += 1) {
const temp = a + b;
a = b;
b = temp;
}
return b;
}
export fn fatorial(n: u32) u64 {
var resultado: u64 = 1;
var i: u32 = 2;
while (i <= n) : (i += 1) {
resultado *= @as(u64, i);
}
return resultado;
}
Compile para WASM:
zig build-lib math.zig -target wasm32-freestanding -dynamic -O ReleaseSmall
Use no JavaScript:
async function carregarWasm() {
const response = await fetch('math.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);
console.log(instance.exports.fibonacci(40)); // Resultado instantâneo
console.log(instance.exports.fatorial(20));
}
carregarWasm();
Construindo Addons Nativos para Node.js
Tradicionalmente, addons nativos para Node.js são escritos em C++. Com Zig, o processo fica mais simples.
// addon.zig
const c = @cImport({
@cInclude("node_api.h");
});
export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
// Registrar funções nativas aqui
var fn_value: c.napi_value = undefined;
_ = c.napi_create_function(env, "hello", 5, &helloNative, null, &fn_value);
_ = c.napi_set_named_property(env, exports, "hello", fn_value);
return exports;
}
fn helloNative(env: c.napi_env, info: c.napi_callback_info) c.napi_value {
_ = info;
var result: c.napi_value = undefined;
_ = c.napi_create_string_utf8(env, "Olá do Zig!", 13, &result);
return result;
}
No lado JavaScript:
const addon = require('./zig-addon.node');
console.log(addon.hello()); // "Olá do Zig!"
npm vs Zig Build System
| npm/package.json | Zig Build System |
|---|---|
npm init | zig init |
package.json | build.zig |
npm install pacote | Dependências via build.zig.zon |
npm run build | zig build |
npm test | zig build test |
node_modules/ | Cache global de pacotes |
Um build.zig.zon típico (equivalente ao package.json):
.{
.name = "meu-projeto",
.version = "0.1.0",
.dependencies = .{
.zap = .{
.url = "https://github.com/zigzap/zap/archive/v0.1.0.tar.gz",
.hash = "...",
},
},
}
Tabela de Referência Rápida
| JavaScript | Zig | Notas |
|---|---|---|
let x = 5 | var x: i32 = 5 | Zig exige tipo ou inferência |
const x = 5 | const x: i32 = 5 | const é realmente imutável |
console.log() | std.debug.print() | Sintaxe de formatação diferente |
null / undefined | null | Tipo optional ?T |
Array | [N]T ou std.ArrayList | Arrays fixos ou dinâmicos |
Object | struct | Campos definidos em compilação |
Promise | std.Thread | Paralelismo real |
JSON.parse | std.json.parseFromSlice | Parsing tipado |
require/import | @import | Sistema de módulos |
=== | == | Zig não tem coerção de tipos |
Conclusão
Aprender Zig como desenvolvedor JavaScript abre uma dimensão completamente nova da programação. Você vai entender o que acontece por baixo do V8, como o Bun consegue ser tão rápido e como escrever código que roda sem runtime. A curva de aprendizado é real, mas cada conceito novo em Zig vai tornar você um desenvolvedor JavaScript mais consciente e eficiente.