Introdução
Go e Zig compartilham valores de simplicidade, mas operam em níveis diferentes. Go tem garbage collector, goroutines e channels; Zig tem allocators manuais, threads do SO e controle de baixo nível. Este guia cobre a conversão prática de código Go para Zig.
Para uma visão conceitual, veja Zig para Programadores Go e Zig vs Go.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja Como Instalar Zig
- Conhecimento de Go
- Familiaridade com Zig. Consulte Introdução ao Zig
Tipos e Variáveis
Go
var nome string = "Zig Brasil"
idade := 25
var valores []int = []int{1, 2, 3}
mapa := map[string]int{"a": 1, "b": 2}
Zig
const nome: []const u8 = "Zig Brasil";
var idade: u32 = 25;
const valores = [_]i32{ 1, 2, 3 };
var mapa = std.StringHashMap(i32).init(allocator);
defer mapa.deinit();
try mapa.put("a", 1);
try mapa.put("b", 2);
Structs e Métodos
Go
type Servidor struct {
Host string
Porta int
ativo bool
}
func (s *Servidor) Iniciar() error {
s.ativo = true
return nil
}
func (s Servidor) Endereco() string {
return fmt.Sprintf("%s:%d", s.Host, s.Porta)
}
Zig
const Servidor = struct {
host: []const u8,
porta: u16,
ativo: bool,
pub fn iniciar(self: *Servidor) !void {
self.ativo = true;
}
pub fn endereco(self: Servidor, buffer: []u8) ![]u8 {
return try std.fmt.bufPrint(buffer, "{s}:{}", .{ self.host, self.porta });
}
};
Interfaces
Go
type Reader interface {
Read(p []byte) (n int, err error)
}
func processar(r Reader) error {
buf := make([]byte, 1024)
n, err := r.Read(buf)
if err != nil {
return err
}
fmt.Println(string(buf[:n]))
return nil
}
Zig
// Opção 1: duck typing via anytype (estático)
fn processar(reader: anytype) !void {
var buf: [1024]u8 = undefined;
const n = try reader.read(&buf);
std.debug.print("{s}\n", .{buf[0..n]});
}
// Opção 2: interface explícita com vtable (dinâmico)
const Reader = struct {
ptr: *anyopaque,
readFn: *const fn (*anyopaque, []u8) usize,
pub fn read(self: Reader, buf: []u8) usize {
return self.readFn(self.ptr, buf);
}
};
Error Handling
Go
func abrir(caminho string) (*os.File, error) {
f, err := os.Open(caminho)
if err != nil {
return nil, fmt.Errorf("erro ao abrir %s: %w", caminho, err)
}
return f, nil
}
f, err := abrir("config.json")
if err != nil {
log.Fatal(err)
}
defer f.Close()
Zig
fn abrir(caminho: []const u8) !std.fs.File {
return std.fs.cwd().openFile(caminho, .{}) catch |err| {
std.log.err("erro ao abrir: {}", .{err});
return err;
};
}
const f = try abrir("config.json");
defer f.close();
Veja Padrões Try/Catch e Error Logging.
Slices
Go
numeros := []int{1, 2, 3, 4, 5}
sub := numeros[1:3]
numeros = append(numeros, 6)
copia := make([]int, len(numeros))
copy(copia, numeros)
Zig
const numeros_fixo = [_]i32{ 1, 2, 3, 4, 5 };
const sub = numeros_fixo[1..3];
// Para crescimento dinâmico, usar ArrayList
var numeros = std.ArrayList(i32).init(allocator);
defer numeros.deinit();
try numeros.appendSlice(&numeros_fixo);
try numeros.append(6);
// Cópia
const copia = try allocator.dupe(i32, numeros.items);
defer allocator.free(copia);
Goroutines e Channels
Go
func main() {
ch := make(chan string, 10)
go func() {
ch <- "resultado"
}()
valor := <-ch
fmt.Println(valor)
}
Zig
const std = @import("std");
pub fn main() !void {
var resultado: []const u8 = undefined;
var mutex = std.Thread.Mutex{};
var cond = std.Thread.Condition{};
const thread = try std.Thread.spawn(.{}, struct {
fn trabalho(r: *[]const u8, m: *std.Thread.Mutex, c: *std.Thread.Condition) void {
m.lock();
defer m.unlock();
r.* = "resultado";
c.signal();
}
}.trabalho, .{ &resultado, &mutex, &cond });
{
mutex.lock();
defer mutex.unlock();
cond.wait(&mutex);
}
std.debug.print("{s}\n", .{resultado});
thread.join();
}
Zig não tem channels nativos. Use mutex, condições, ou atomics. Veja Concorrência em Zig.
Defer
Go e Zig têm defer, mas com semântica diferente:
- Go:
deferexecuta na saída da função, em ordem LIFO - Zig:
deferexecuta na saída do escopo (bloco), em ordem LIFO
fn exemplo() void {
{
const recurso = obterRecurso();
defer liberarRecurso(recurso);
// recurso liberado ao sair deste bloco
}
// recurso já foi liberado aqui
}
Zig também tem errdefer — sem equivalente em Go:
fn criarConexao(allocator: std.mem.Allocator) !*Conexao {
const conn = try allocator.create(Conexao);
errdefer allocator.destroy(conn); // só executa se retornar erro
try conn.conectar();
return conn;
}
Veja Padrões Errdefer.
Gerenciamento de Memória
Go (GC automático)
func criarLista() []string {
lista := make([]string, 0)
lista = append(lista, "item1", "item2")
return lista // GC cuida da memória
}
Zig (manual via allocators)
fn criarLista(allocator: std.mem.Allocator) !std.ArrayList([]const u8) {
var lista = std.ArrayList([]const u8).init(allocator);
errdefer lista.deinit();
try lista.append("item1");
try lista.append("item2");
return lista;
}
// O chamador gerencia o ciclo de vida:
var lista = try criarLista(allocator);
defer lista.deinit();
Veja Substituir malloc/free por Allocators e ArenaAllocator.
Go Modules para build.zig
go.mod
module meu-projeto
go 1.21
require github.com/pkg/errors v0.9.1
build.zig
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "meu-projeto",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Dependências via build.zig.zon
b.installArtifact(exe);
}
Testes
Go
func TestSoma(t *testing.T) {
if resultado := soma(2, 3); resultado != 5 {
t.Errorf("esperava 5, obteve %d", resultado)
}
}
func BenchmarkSoma(b *testing.B) {
for i := 0; i < b.N; i++ {
soma(2, 3)
}
}
Zig
test "soma" {
try std.testing.expectEqual(@as(i32, 5), soma(2, 3));
}
// Benchmarks via testes customizados
test "benchmark soma" {
const timer = try std.time.Timer.start();
var i: usize = 0;
while (i < 1_000_000) : (i += 1) {
_ = soma(2, 3);
}
const elapsed = timer.read();
std.debug.print("Tempo: {}ns\n", .{elapsed});
}
Veja Testes Unitários e Benchmarking.
Conclusão
A migração de Go para Zig troca a conveniência do garbage collector e goroutines pela performance previsível e controle de baixo nível. As maiores adaptações são o gerenciamento manual de memória e a ausência de channels/goroutines nativos.
Para avaliar se a migração faz sentido, leia Quando Usar Zig. Para começar com Zig, visite Introdução ao Zig.