Um dos maiores diferenciais da linguagem Zig é sua capacidade de cross-compilation integrada ao compilador. Diferente de outras linguagens onde configurar uma toolchain para compilar para outro sistema operacional pode levar horas, Zig torna o processo trivial — e em 2026, o suporte a FreeBSD e NetBSD atingiu maturidade de produção.
Neste artigo, vamos explorar como compilar projetos Zig para sistemas BSD, usar zig cc como cross-compiler C para BSD, e configurar pipelines de CI/CD que geram binários para múltiplos targets BSD.
Por Que BSD Importa para Desenvolvedores Zig
FreeBSD e NetBSD são sistemas operacionais amplamente usados em infraestrutura de produção. Netflix, WhatsApp e Sony (PlayStation) usam FreeBSD em seus servidores e dispositivos. NetBSD é conhecido por rodar em praticamente qualquer hardware existente.
Para desenvolvedores de sistemas e ferramentas de baixo nível, poder compilar para BSD a partir de qualquer máquina de desenvolvimento — seja Linux, macOS ou Windows — é essencial. Zig elimina a necessidade de ter uma máquina BSD disponível para compilar.
O Que Mudou em 2026
O suporte a BSD no Zig evoluiu significativamente:
- aarch64-freebsd e x86_64-freebsd são agora targets testados nativamente no CI do Zig
- aarch64-netbsd e x86_64-netbsd também ganharam testes nativos no CI
- Zig agora inclui headers de sistema e libc para FreeBSD 14.0+ e NetBSD 10.1+
- O formato
abilistscompacto permite que o compilador conheça todos os símbolos públicos da libc de cada target
Compilando Zig para FreeBSD
Exemplo Básico
Compilar um programa Zig para FreeBSD é tão simples quanto adicionar o flag -target:
// src/main.zig — servidor HTTP simples
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var server = std.http.Server.init(allocator, .{
.reuse_address = true,
});
defer server.deinit();
const address = std.net.Address.parseIp("0.0.0.0", 8080) catch unreachable;
try server.listen(address);
std.log.info("Servidor rodando na porta 8080", .{});
while (true) {
var response = try server.accept(.{
.allocator = allocator,
});
defer response.deinit();
try response.headers.append("Content-Type", "text/plain");
try response.do();
try response.writeAll("Zig rodando em FreeBSD!");
try response.finish();
}
}
Para compilar esse servidor para FreeBSD x86_64:
# Compilar para FreeBSD x86_64
zig build-exe src/main.zig -target x86_64-freebsd -O ReleaseSafe
# Compilar para FreeBSD ARM64 (ex: servidor ARM)
zig build-exe src/main.zig -target aarch64-freebsd -O ReleaseSafe
# Verificar o binário gerado
file main
# main: ELF 64-bit LSB executable, x86-64, version 1 (FreeBSD)
Usando o Build System
Para projetos maiores que usam o build system do Zig, a configuração de targets BSD é feita no build.zig:
const std = @import("std");
pub fn build(b: *std.Build) void {
// Target padrão (máquina local)
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "meu-servidor",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
// Steps dedicados para cada target BSD
const freebsd_x86 = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .freebsd,
});
const freebsd_arm = b.resolveTargetQuery(.{
.cpu_arch = .aarch64,
.os_tag = .freebsd,
});
const netbsd_x86 = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .netbsd,
});
// Executáveis para cada plataforma
const exe_freebsd_x86 = b.addExecutable(.{
.name = "meu-servidor-freebsd-x86",
.root_source_file = b.path("src/main.zig"),
.target = freebsd_x86,
.optimize = .ReleaseSafe,
});
const exe_freebsd_arm = b.addExecutable(.{
.name = "meu-servidor-freebsd-arm",
.root_source_file = b.path("src/main.zig"),
.target = freebsd_arm,
.optimize = .ReleaseSafe,
});
const exe_netbsd = b.addExecutable(.{
.name = "meu-servidor-netbsd-x86",
.root_source_file = b.path("src/main.zig"),
.target = netbsd_x86,
.optimize = .ReleaseSafe,
});
// Step "bsd" compila todos os targets BSD
const bsd_step = b.step("bsd", "Compilar para todos os targets BSD");
bsd_step.dependOn(&b.addInstallArtifact(exe_freebsd_x86, .{}).step);
bsd_step.dependOn(&b.addInstallArtifact(exe_freebsd_arm, .{}).step);
bsd_step.dependOn(&b.addInstallArtifact(exe_netbsd, .{}).step);
}
Agora, basta rodar:
# Compilar para todos os targets BSD
zig build bsd
# Ou especificar target via linha de comando
zig build -Dtarget=x86_64-freebsd
Compilando Zig para NetBSD
O processo para NetBSD é idêntico, mudando apenas o target:
# NetBSD x86_64
zig build-exe src/main.zig -target x86_64-netbsd -O ReleaseSafe
# NetBSD ARM64
zig build-exe src/main.zig -target aarch64-netbsd -O ReleaseSafe
Diferenças entre FreeBSD e NetBSD como Targets
Embora a experiência do desenvolvedor seja a mesma, há diferenças nas syscalls e APIs disponíveis:
const std = @import("std");
const builtin = @import("builtin");
pub fn getPlatformInfo() []const u8 {
return switch (builtin.os.tag) {
.freebsd => "FreeBSD — kqueue, jails, ZFS nativo",
.netbsd => "NetBSD — portabilidade extrema, rump kernels",
.linux => "Linux — epoll, cgroups, namespaces",
else => "Plataforma desconhecida",
};
}
// Adaptar comportamento por SO em comptime
pub fn createEventLoop() !void {
if (comptime builtin.os.tag == .freebsd or builtin.os.tag == .netbsd) {
// BSD usa kqueue para I/O assíncrono
std.log.info("Usando kqueue para event loop", .{});
} else if (comptime builtin.os.tag == .linux) {
// Linux usa epoll (ou io_uring)
std.log.info("Usando epoll/io_uring para event loop", .{});
}
}
Para entender mais sobre I/O assíncrono em Zig, veja o artigo sobre io_uring e async e os padrões de concorrência.
Usando zig cc como Cross-Compiler C para BSD
Um caso de uso extremamente poderoso é usar zig cc para compilar projetos C existentes para BSD. O Zig embarca sua própria libc e headers de sistema para cada target suportado, eliminando a necessidade de instalar toolchains separadas.
Compilando um Projeto C para FreeBSD
# Compilar um arquivo C para FreeBSD
zig cc -target x86_64-freebsd -O2 -o servidor servidor.c
# Compilar com linkagem a bibliotecas do sistema
zig cc -target x86_64-freebsd -O2 -lm -lpthread -o app main.c utils.c
# Compilar código C++ para FreeBSD
zig c++ -target x86_64-freebsd -O2 -std=c++17 -o app main.cpp
Integração com Projetos C Existentes via Makefile
# Makefile para cross-compilation BSD com zig cc
CC = zig cc
CXX = zig c++
FREEBSD_TARGET = x86_64-freebsd
NETBSD_TARGET = x86_64-netbsd
CFLAGS = -O2 -Wall -Wextra
SRC = src/main.c src/utils.c src/network.c
.PHONY: freebsd netbsd all
all: freebsd netbsd
freebsd:
$(CC) -target $(FREEBSD_TARGET) $(CFLAGS) $(SRC) -o build/app-freebsd
netbsd:
$(CC) -target $(NETBSD_TARGET) $(CFLAGS) $(SRC) -o build/app-netbsd
Isso é particularmente útil para quem está migrando projetos de C para Zig gradualmente — você pode compilar a parte C do projeto com zig cc e a parte nova com Zig nativo, tudo no mesmo pipeline de build.
Integração com a Interoperabilidade C de Zig
Projetos que usam a interoperabilidade C do Zig para chamar bibliotecas C nativas funcionam perfeitamente com targets BSD:
const c = @cImport({
@cInclude("sys/sysctl.h");
@cInclude("sys/types.h");
});
pub fn getSystemInfo(allocator: std.mem.Allocator) ![]const u8 {
var size: usize = 0;
// sysctl está disponível em FreeBSD e NetBSD
const name = [_]c_int{ c.CTL_KERN, c.KERN_OSRELEASE };
_ = c.sysctl(&name, name.len, null, &size, null, 0);
const buf = try allocator.alloc(u8, size);
_ = c.sysctl(&name, name.len, buf.ptr, &size, null, 0);
return buf[0..size];
}
Pipeline de CI/CD Multi-Target com BSD
Para projetos que precisam gerar releases para múltiplos sistemas operacionais, um pipeline automatizado é essencial. Aqui está um exemplo usando o Docker com Zig:
# .gitea/workflows/release.yml
name: Release Multi-Plataforma
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
target:
- x86_64-linux
- aarch64-linux
- x86_64-freebsd
- aarch64-freebsd
- x86_64-netbsd
steps:
- uses: actions/checkout@v4
- name: Instalar Zig
run: |
wget -q https://ziglang.org/download/0.16.0/zig-linux-x86_64-0.16.0.tar.xz
tar xf zig-linux-x86_64-0.16.0.tar.xz
echo "$PWD/zig-linux-x86_64-0.16.0" >> $GITHUB_PATH
- name: Compilar para ${{ matrix.target }}
run: zig build -Dtarget=${{ matrix.target }} -Doptimize=ReleaseSafe
- name: Upload artefato
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.target }}
path: zig-out/bin/*
Este pipeline compila seu projeto para Linux, FreeBSD e NetBSD automaticamente a cada nova tag. Veja o guia sobre cross-compilation para configurações mais avançadas.
Considerações sobre Alocação de Memória em BSD
Os allocators do Zig funcionam de forma consistente entre Linux e BSD, mas há nuances a considerar:
const std = @import("std");
const builtin = @import("builtin");
pub fn createOptimalAllocator() std.mem.Allocator {
// page_allocator usa mmap em todos os Unix-like (Linux, FreeBSD, NetBSD)
// O comportamento é consistente entre plataformas
return std.heap.page_allocator;
}
// Para aplicações de alta performance em servidores BSD
pub fn serverAllocator() std.heap.GeneralPurposeAllocator(.{
.thread_safe = true,
// Habilitar safety em produção para detectar bugs
.safety = builtin.mode == .Debug or builtin.mode == .ReleaseSafe,
}) {
return .{};
}
O page_allocator do Zig usa mmap tanto em Linux quanto em BSD, garantindo comportamento consistente. Para cenários de alta performance, como processamento de dados ou networking, os mesmos padrões de alocação se aplicam.
Comparação: Cross-Compilation para BSD em Diferentes Linguagens
| Aspecto | Zig | Go | Rust | C/C++ |
|---|---|---|---|---|
| Setup necessário | Nenhum (embutido) | GOOS=freebsd | Instalar target + linker | Toolchain completa |
| Suporte FreeBSD | Nativo, CI testado | Nativo | Via rustup target | Manual |
| Suporte NetBSD | Nativo, CI testado | Parcial | Tier 3 (sem CI) | Manual |
| Libc incluída | Sim (headers + abilists) | Sim (Go runtime) | Depende do target | Não |
| Cross-compile C | Sim (zig cc) | Via CGO (complexo) | Via cc-rs (complexo) | Toolchain externa |
| Binário estático | Padrão | Padrão | Com musl | Com musl |
Para uma comparação mais detalhada entre Zig e outras linguagens, veja os artigos Zig vs Rust, Zig vs Go e o comparativo completo.
Desenvolvedores que usam Rust rustlang.com.br notarão que o suporte BSD no Zig é significativamente mais simples, sem necessidade de instalar targets adicionais via rustup. Já quem vem de Go golang.com.br encontrará uma experiência similar, mas com o bônus de poder cross-compilar código C junto.
Deploy em Servidores FreeBSD
Após compilar, enviar o binário para um servidor FreeBSD é direto:
# Compilar localmente (de qualquer SO)
zig build -Dtarget=x86_64-freebsd -Doptimize=ReleaseSafe
# Enviar para o servidor
scp zig-out/bin/meu-servidor usuario@servidor-freebsd:/usr/local/bin/
# No servidor FreeBSD — criar service rc.d
ssh usuario@servidor-freebsd << 'EOF'
cat > /usr/local/etc/rc.d/meu-servidor << 'RC'
#!/bin/sh
# PROVIDE: meu_servidor
# REQUIRE: NETWORKING
# KEYWORD: shutdown
. /etc/rc.subr
name="meu_servidor"
rcvar="meu_servidor_enable"
command="/usr/local/bin/meu-servidor"
pidfile="/var/run/${name}.pid"
load_rc_config $name
run_rc_command "$1"
RC
chmod +x /usr/local/etc/rc.d/meu-servidor
echo 'meu_servidor_enable="YES"' >> /etc/rc.conf
service meu_servidor start
EOF
Perguntas Frequentes
Preciso de uma máquina FreeBSD para compilar para FreeBSD?
Não. Esse é o grande diferencial do Zig: o compilador inclui todos os headers e informações de libc necessários para gerar binários FreeBSD e NetBSD a partir de qualquer sistema operacional. Basta usar -target x86_64-freebsd ou -target aarch64-freebsd.
O zig cc funciona como substituto do clang no FreeBSD?
Sim, para a maioria dos casos. zig cc é um wrapper sobre o backend LLVM que inclui libc e headers de sistema. Projetos C que compilam com clang ou gcc normalmente compilam com zig cc sem alterações — inclusive para targets BSD.
Quais versões de FreeBSD e NetBSD são suportadas?
O Zig 0.16 suporta oficialmente FreeBSD 14.0+ e NetBSD 10.1+. O compilador inclui headers e abilists destas versões. Versões mais antigas podem funcionar, mas não são testadas no CI oficial.
Posso usar bibliotecas nativas do FreeBSD como jails ou ZFS?
Sim, via @cImport do Zig. Você pode importar headers do FreeBSD e chamar APIs nativas como jail_attach(), funções de libzfs, ou qualquer API disponível nos headers incluídos. A interoperabilidade C do Zig facilita esse processo.