Guia de Migração: C para Zig

Introdução

Este guia técnico cobre a conversão de código C para Zig de forma prática, com exemplos lado a lado para cada padrão comum. Se você está planejando uma migração de projeto, leia primeiro Como Migrar um Projeto C para Zig para a estratégia de alto nível.

Pré-requisitos

Tipos Fundamentais

Tipo CTipo ZigNotas
intc_int ou i32c_int para interop, i32 para código puro Zig
unsigned intc_uint ou u32
charu8
floatf32
doublef64
size_tusize
ssize_tisize
void**anyopaquePonteiro opaco
NULLnullPara optionals ?*T
boolbool
int8_ti8
uint64_tu64

Variáveis e Constantes

C

const int MAX = 100;
int contador = 0;
static int privado = 42;

Zig

const MAX: i32 = 100;
var contador: i32 = 0;
const privado: i32 = 42; // visibilidade controlada por pub

Em Zig, const é imutável de verdade (não apenas “promessa ao compilador” como em C). Variáveis são var. Não existe static local — use variáveis no escopo do módulo.

Ponteiros

C

int* ptr = &valor;
int** ptr_ptr = &ptr;
void* generico = ptr;
int* array = malloc(10 * sizeof(int));

Zig

const ptr: *i32 = &valor;
const ptr_ptr: **i32 = &ptr;
const generico: *anyopaque = @ptrCast(ptr);
const array = try allocator.alloc(i32, 10);
defer allocator.free(array);

Para detalhes completos sobre conversão de ponteiros, veja Converter Ponteiros C para Zig.

Ponteiros Nullable

// C: qualquer ponteiro pode ser NULL
int* talvez_null = funcao_que_pode_retornar_null();
if (talvez_null != NULL) {
    printf("%d\n", *talvez_null);
}
// Zig: nullabilidade é explícita no tipo
const talvez_null: ?*i32 = funcaoQuePodeRetornarNull();
if (talvez_null) |ptr| {
    std.debug.print("{}\n", .{ptr.*});
}

Strings

C

const char* str = "Olá mundo";
char buffer[256];
snprintf(buffer, sizeof(buffer), "Valor: %d", 42);
size_t len = strlen(str);
int cmp = strcmp(str1, str2);

Zig

const str: []const u8 = "Olá mundo";
var buffer: [256]u8 = undefined;
const escrito = try std.fmt.bufPrint(&buffer, "Valor: {}", .{42});
const len = str.len;
const igual = std.mem.eql(u8, str1, str2);

Strings em Zig são slices de bytes ([]const u8) com tamanho conhecido, não null-terminated. Para converter: std.mem.span(c_string).

Structs

C

typedef struct {
    double x;
    double y;
} Ponto;

Ponto criar_ponto(double x, double y) {
    Ponto p = {.x = x, .y = y};
    return p;
}

Zig

const Ponto = struct {
    x: f64,
    y: f64,

    pub fn criar(x: f64, y: f64) Ponto {
        return .{ .x = x, .y = y };
    }
};

Macros para Comptime

O preprocessador C é substituído por comptime. Veja Substituir Macros C por Comptime Zig para o guia completo.

Constantes

#define MAX_BUFFER 4096
#define PI 3.14159265358979
const MAX_BUFFER: usize = 4096;
const PI: f64 = 3.14159265358979;

Macros de Função

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
fn min(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
    return if (a < b) a else b;
}

fn arraySize(comptime T: type, arr: []const T) usize {
    return arr.len; // slices já carregam o tamanho
}

Compilação Condicional

#ifdef DEBUG
    printf("Debug: %s\n", msg);
#endif

#if defined(__linux__)
    // código Linux
#elif defined(_WIN32)
    // código Windows
#endif
if (builtin.mode == .Debug) {
    std.debug.print("Debug: {s}\n", .{msg});
}

if (builtin.os.tag == .linux) {
    // código Linux
} else if (builtin.os.tag == .windows) {
    // código Windows
}

Alocação de Memória

C

int* arr = malloc(100 * sizeof(int));
if (arr == NULL) { /* erro */ }
// usar arr...
free(arr);

void* novo = realloc(arr, 200 * sizeof(int));

Zig

const arr = try allocator.alloc(i32, 100);
defer allocator.free(arr);
// usar arr...

// Para realloc:
const novo = try allocator.realloc(arr, 200);

Veja Substituir malloc/free por Allocators Zig e ArenaAllocator.

Tratamento de Erros

C

int resultado = operacao();
if (resultado < 0) {
    fprintf(stderr, "Erro: %s\n", strerror(errno));
    return -1;
}

Zig

const resultado = operacao() catch |err| {
    std.debug.print("Erro: {}\n", .{err});
    return err;
};

// Ou com try (propaga automaticamente)
const resultado2 = try operacao();

Veja Error Sets Customizados e Padrões Try/Catch.

Loops

C

for (int i = 0; i < n; i++) {
    processar(arr[i]);
}

while (condicao) {
    // ...
}

do {
    // ...
} while (condicao);

Zig

// Preferir for range-based
for (arr) |item| {
    processar(item);
}

// Com índice
for (arr, 0..) |item, i| {
    processar(item, i);
}

// While
while (condicao) {
    // ...
}

// Não há do-while; usar while com break:
while (true) {
    // ...
    if (!condicao) break;
}

Ferramenta zig translate-c

Zig inclui uma ferramenta que traduz código C para Zig automaticamente:

zig translate-c arquivo.c > arquivo.zig

O resultado precisa de revisão manual — a tradução é literal e não idiomática. Use como ponto de partida, depois refatore para Zig idiomático.

Padrão de Migração de Arquivo

  1. Traduzir com zig translate-c (ponto de partida)
  2. Substituir tipos C por tipos Zig nativos
  3. Converter ponteiros para slices quando possível
  4. Substituir malloc/free por allocators
  5. Converter macros para comptime
  6. Adicionar error handling via error unions
  7. Escrever testes (test "...") — veja Testes Unitários
  8. Remover o arquivo .c original do build

Conclusão

A migração de C para Zig é um processo incremental que pode ser feito arquivo por arquivo. A interoperabilidade transparente garante que o projeto funcione em cada etapa. O resultado é código mais seguro, mais legível e mais fácil de manter.

Para a estratégia de migração de projeto completo, veja Como Migrar um Projeto C para Zig. Para portar bibliotecas C, consulte Portar uma Biblioteca C para Zig.

Continue aprendendo Zig

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