Zig e C: Interoperabilidade Completa - Guia Prático

A interoperabilidade com C é uma das features mais poderosas de Zig. Diferente de outras linguagens que exigem bindings manuais, ferramentas externas ou camadas complexas de abstração, Zig permite importar headers C diretamente e trabalhar com código C nativamente. Neste guia completo, vamos explorar todas as técnicas de interoperabilidade Zig-C com exemplos práticos.

Por Que a Interoperabilidade C é Importante

O Legado C é Imenso

O ecossistema C representa décadas de desenvolvimento de software:

  • Sistemas operacionais: Linux kernel, Windows APIs, POSIX
  • Bibliotecas fundamentais: OpenSSL, zlib, SQLite, libpng
  • Drivers e hardware: Praticamente todo driver de dispositivo
  • Protocolos de rede: TCP/IP, HTTP/2, QUIC implementações
  • Criptografia: OpenSSL, libsodium, GnuTLS
  • Compressão: zlib, bzip2, lz4

Reescrever tudo isso em Zig seria inviável. A solução: usar diretamente.

Zig como Substituto de Compilador C

Zig pode não apenas chamar código C, mas também compilar código C:

# Zig como substituto do gcc/clang
zig cc main.c -o programa
zig c++ main.cpp -o programa_cpp

# Cross-compilation trivial
zig cc -target aarch64-linux-gnu main.c -o programa_arm

Isso significa que você pode usar Zig como toolchain completa para projetos C existentes.

Fundamentos: Chamando Funções C

O Básico: @cImport e @cInclude

A magia começa com @cImport, que importa definições C diretamente para Zig:

const c = @cImport({
    @cInclude("stdio.h");
});

pub fn main() void {
    _ = c.printf("Hello do C!\n");
}

O que acontece aqui:

  1. @cInclude("stdio.h") diz ao compilador para processar o header C
  2. @cImport converte as definições C em declarações Zig
  3. c se torna um namespace contendo todas as definições do header

Exemplo Prático: Usando a Libc

const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
    @cInclude("string.h");
    @cInclude("math.h");
});

pub fn main() !void {
    // printf
    _ = c.printf("=== Testando Libc ===\n");
    
    // malloc/free
    const ptr = c.malloc(100);
    if (ptr == null) {
        return error.OutOfMemory;
    }
    defer c.free(ptr); // Liberamos a memória C com free C!
    
    // strcpy
    const msg: [*c]u8 = @ptrCast(ptr);
    _ = c.strcpy(msg, "Olá do Zig!");
    _ = c.printf("Mensagem: %s\n", msg);
    
    // funções matemáticas
    const raiz = c.sqrt(2.0);
    _ = c.printf("Raiz de 2: %f\n", raiz);
    
    // random
    c.srand(@intCast(c.time(null)));
    const aleatorio = c.rand();
    _ = c.printf("Número aleatório: %d\n", aleatorio);
}

Nota importante: Zig converte automaticamente:

  • intc_int
  • char*[*c]u8 (ponteiro C para u8)
  • doublef64
  • void**anyopaque

Tipos C em Zig

Tabela de Conversão de Tipos

Ao importar código C, Zig converte os tipos automaticamente:

Tipo CTipo ZigObservação
charc_charPode ser i8 ou u8 dependendo da plataforma
shortc_shortGeralmente i16
intc_intGeralmente i32
longc_longDepende da plataforma (i32 ou i64)
long longc_longlongGeralmente i64
floatf32Ponto flutuante 32-bit
doublef64Ponto flutuante 64-bit
void**anyopaquePonteiro para tipo opaco
char*[*c]u8Ponteiro C nulo-terminado
const char*[*c]const u8Ponteiro C constante
size_tc_size_tTipo de tamanho da plataforma
FILE**c.FILEPonteiro para struct FILE
struct Xc.struct_XStruct com prefixo
enum Xc.enum_XEnum com prefixo
union Xc.union_XUnion com prefixo
typedefPreservadoO alias é mantido

Ponteiros C vs Ponteiros Zig

A diferença mais importante:

// Ponteiro Zig: NUNCA nulo, bounds checking
var ptr_zig: *i32 = &valor;

// Ponteiro C: Pode ser nulo, sem bounds checking
var ptr_c: [*c]i32 = c.malloc(100);
if (ptr_c == null) { /* erro */ }

Padrão idiomático: Converta para slices Zig quando possível:

const c = @cImport(@cInclude("stdlib.h"));

// Alocação C
const ptr_c = c.malloc(100 * @sizeOf(i32));
if (ptr_c == null) return error.OutOfMemory;
defer c.free(ptr_c);

// Converter para slice Zig (seguro!)
const slice: []i32 = @as([*]i32, @ptrCast(ptr_c))[0..100];
slice[0] = 42; // Agora temos bounds checking!
slice[50] = 100;

Importando Bibliotecas C Externas

Configurando o build.zig

Para usar uma biblioteca C, você precisa configurar o build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "meu_programa",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // === Configurações para bibliotecas C ===
    
    // 1. Adicionar caminho de includes
    exe.addIncludePath(b.path("include/"));
    
    // 2. Adicionar caminho de bibliotecas
    exe.addLibraryPath(b.path("lib/"));
    
    // 3. Linkar com bibliotecas específicas
    exe.linkSystemLibrary("sqlite3");    // -lsqlite3
    exe.linkSystemLibrary("curl");       // -lcurl
    exe.linkSystemLibrary("ssl");        // -lssl
    exe.linkSystemLibrary("crypto");     // -lcrypto
    
    // 4. Linkar com a libc (necessário para a maioria das libs C)
    exe.linkLibC();

    b.installArtifact(exe);
}

Exemplo Completo: Usando SQLite

// src/main.zig
const std = @import("std");
const c = @cImport({
    @cInclude("sqlite3.h");
});

const DatabaseError = error{
    OpenFailed,
    QueryFailed,
    PrepareFailed,
};

const Database = struct {
    db: ?*c.sqlite3,

    pub fn open(path: []const u8) DatabaseError!Database {
        var db: ?*c.sqlite3 = null;
        
        // Converter slice Zig para ponteiro C nulo-terminado
        const c_path = @ptrCast([*c]const u8, path.ptr);
        
        const rc = c.sqlite3_open(c_path, &db);
        if (rc != c.SQLITE_OK) {
            std.debug.print("Erro ao abrir DB: {s}\n", .{c.sqlite3_errmsg(db)});
            return DatabaseError.OpenFailed;
        }
        
        return Database{ .db = db };
    }

    pub fn close(self: *Database) void {
        if (self.db) |db| {
            _ = c.sqlite3_close(db);
            self.db = null;
        }
    }

    pub fn execute(self: Database, sql: []const u8) DatabaseError!void {
        const c_sql = @ptrCast([*c]const u8, sql.ptr);
        const rc = c.sqlite3_exec(self.db, c_sql, null, null, null);
        
        if (rc != c.SQLITE_OK) {
            return DatabaseError.QueryFailed;
        }
    }
};

pub fn main() !void {
    var db = try Database.open(":memory:");
    defer db.close();

    try db.execute(
        \\CREATE TABLE users (
        \\  id INTEGER PRIMARY KEY,
        \\  name TEXT NOT NULL
        \\);
    );

    try db.execute(
        \\INSERT INTO users (name) VALUES ('Alice'), ('Bob');
    );

    std.debug.print("Banco de dados criado com sucesso!\n", .{});
}
// build.zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "sqlite_demo",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // Linkar com SQLite
    exe.linkSystemLibrary("sqlite3");
    exe.linkLibC();

    b.installArtifact(exe);
}

Convertendo Strings entre C e Zig

C String → Zig Slice

const std = @import("std");
const c = @cImport(@cInclude("string.h"));

// Ponteiro C para string
const c_string: [*c]const u8 = c.getenv("HOME");

// Método 1: Calcular length com strlen
const len = c.strlen(c_string);
const zig_slice: []const u8 = c_string[0..len];

// Método 2: Usar std.mem.span (mais idiomático)
const zig_slice2: [:0]const u8 = std.mem.span(c_string);
// ^ [:0] significa "slice nulo-terminado"

Zig Slice → C String

const std = @import("std");

// Slice Zig normal
const zig_string: []const u8 = "Olá, C!";

// Método 1: Garantir nulo no final (se já for string literal)
const c_string: [*c]const u8 = "Olá, C!"; // String literals já são nulo-terminadas

// Método 2: Alocar com nulo para slices dinâmicos
fn toCString(allocator: std.mem.Allocator, slice: []const u8) ![:0]u8 {
    // Aloca espaço extra para o nulo
    var result = try allocator.allocSentinel(u8, slice.len, 0);
    @memcpy(result[0..slice.len], slice);
    return result;
}

// Uso
const c_str = try toCString(allocator, zig_string);
defer allocator.free(c_str);

Exemplo Prático: Wrapping uma Função C

const std = @import("std");
const c = @cImport({
    @cInclude("curl/curl.h");
});

// Wrapper idiomático em Zig
pub fn fetchUrl(allocator: std.mem.Allocator, url: []const u8) ![]u8 {
    const curl = c.curl_easy_init();
    if (curl == null) return error.CurlInitFailed;
    defer c.curl_easy_cleanup(curl);
    
    // Converter URL para C string
    const c_url = try allocator.dupeZ(u8, url); // dupeZ = duplicar com zero no final
    defer allocator.free(c_url);
    
    // Configurar CURL
    _ = c.curl_easy_setopt(curl, c.CURLOPT_URL, c_url.ptr);
    _ = c.curl_easy_setopt(curl, c.CURLOPT_FOLLOWLOCATION, @as(c_long, 1));
    
    // Executar
    const res = c.curl_easy_perform(curl);
    if (res != c.CURLE_OK) {
        return error.CurlRequestFailed;
    }
    
    return "Requisição realizada com sucesso";
}

Trabalhando com Structs C

Mapeando Structs

// mylib.h
typedef struct {
    int x;
    int y;
    char name[64];
    void* user_data;
} Point;

Point* point_create(int x, int y);
void point_destroy(Point* p);
void point_move(Point* p, int dx, int dy);
// main.zig
const std = @import("std");
const c = @cImport({
    @cInclude("mylib.h");
});

// O struct é importado como c.Point
pub fn main() void {
    // Criar instância
    const p = c.point_create(10, 20);
    if (p == null) {
        std.debug.print("Falha ao criar ponto\n", .{});
        return;
    }
    defer c.point_destroy(p);
    
    // Acessar campos
    std.debug.print("Ponto: x={}, y={}\n", .{ p.?.x, p.?.y });
    
    // Modificar
    c.point_move(p, 5, -3);
    std.debug.print("Após mover: x={}, y={}\n", .{ p.?.x, p.?.y });
    
    // Acessar array
    const name_slice = &p.?.name;
    std.debug.print("Nome: {s}\n", .{std.mem.sliceTo(name_slice, 0)});
}

Criando Wrappers Idiomáticos

const Point = struct {
    raw: *c.Point,
    
    pub fn new(x: c_int, y: c_int) !Point {
        const raw = c.point_create(x, y);
        if (raw == null) return error.OutOfMemory;
        return Point{ .raw = raw };
    }
    
    pub fn deinit(self: Point) void {
        c.point_destroy(self.raw);
    }
    
    pub fn move(self: Point, dx: c_int, dy: c_int) void {
        c.point_move(self.raw, dx, dy);
    }
    
    pub fn x(self: Point) c_int {
        return self.raw.x;
    }
    
    pub fn y(self: Point) c_int {
        return self.raw.y;
    }
};

// Uso idiomático em Zig
pub fn main() !void {
    const p = try Point.new(10, 20);
    defer p.deinit();
    
    p.move(5, -3);
    std.debug.print("Posição: ({}, {})\n", .{ p.x(), p.y() });
}

Callbacks e Funções de Ponteiro

Passando Funções Zig para C

Muitas bibliotecas C usam callbacks. Em Zig, você pode passar funções diretamente:

const c = @cImport({
    @cInclude("stdlib.h");
});

// Função de comparação para qsort
fn compareInts(a: ?*const anyopaque, b: ?*const anyopaque) callconv(.C) c_int {
    const a_int: *const c_int = @ptrCast(@alignCast(a));
    const b_int: *const c_int = @ptrCast(@alignCast(b));
    
    if (a_int.* < b_int.*) return -1;
    if (a_int.* > b_int.*) return 1;
    return 0;
}

pub fn main() void {
    var nums = [_]c_int{ 5, 2, 8, 1, 9, 3 };
    
    c.qsort(
        &nums,
        nums.len,
        @sizeOf(c_int),
        compareInts  // Passamos a função Zig como callback C!
    );
    
    // nums agora está ordenado: {1, 2, 3, 5, 8, 9}
}

Importante: Use callconv(.C) para funções que serão chamadas por código C.

Recebendo Callbacks C em Zig

// Simulando uma biblioteca C que chama callbacks
typedef void (*ProgressCallback)(int percent, void* user_data);
void do_work(ProgressCallback cb, void* user_data);
const c = @cImport(@cInclude("worker.h"));

const Context = struct {
    total_items: usize,
    processed: usize,
};

fn onProgress(percent: c_int, user_data: ?*anyopaque) callconv(.C) void {
    const ctx: *Context = @ptrCast(@alignCast(user_data));
    std.debug.print("Progresso: {}% ({}/{} items)\n", .{
        percent,
        ctx.processed,
        ctx.total_items
    });
}

pub fn main() void {
    var ctx = Context{
        .total_items = 100,
        .processed = 0,
    };
    
    c.do_work(onProgress, &ctx);
}

Gerenciamento de Memória Entre Fronteiras

Regras Fundamentais

  1. Quem aloca, libera: Se C alocou com malloc, libere com free
  2. Se Zig alocou, Zig libera: Use o mesmo allocator
  3. Não misture allocators: Não passe memória Zig para free C
const c = @cImport({
    @cInclude("stdlib.h");
    @cInclude("string.h");
});

// ❌ ERRADO: Misturando allocators
pub fn wrong() void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    
    const zig_mem = try allocator.alloc(u8, 100);
    c.free(zig_mem); // ERRO! zig_mem não foi alocado com malloc
}

// ✅ CORRETO: Cada lado gerencia sua memória
pub fn correct() !void {
    // Memória C
    const c_mem = c.malloc(100);
    if (c_mem == null) return error.OutOfMemory;
    defer c.free(c_mem); // Libera com free
    
    // Memória Zig
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    
    const zig_mem = try allocator.alloc(u8, 100);
    defer allocator.free(zig_mem); // Libera com allocator
}

Padrão: Allocator Zig + Free C

Quando você precisa que C libere memória que veio de Zig:

// Alocar com allocator Zig
const buffer = try allocator.alloc(u8, size);

// Passar para C (C não deve liberar!)
c.some_function(buffer.ptr);

// Liberar no Zig quando C terminar
allocator.free(buffer);

Padrão: Arena Allocator para C

Para situações complexas, use um arena allocator:

pub fn processWithC(allocator: std.mem.Allocator) !void {
    // Criar arena
    var arena = std.heap.ArenaAllocator.init(allocator);
    defer arena.deinit();
    const arena_allocator = arena.allocator();
    
    // Alocar tudo que C precisa na arena
    const buf1 = try arena_allocator.alloc(u8, 100);
    const buf2 = try arena_allocator.alloc(u8, 200);
    const buf3 = try arena_allocator.dupe(u8, "dados");
    
    // Passar para C
    c.process_data(buf1.ptr, buf2.ptr, buf3.ptr);
    
    // Toda a memória é liberada de uma vez no defer
}

Traduzindo C para Zig Automaticamente

zig translate-c

Zig pode traduzir código C inteiro para Zig automaticamente:

# Traduzir um header
zig translate-c mylib.h > mylib.zig

# Traduzir com defines
zig translate-c -DMY_DEFINE=1 mylib.h

# Traduzir com includes específicos
zig translate-c -I/usr/local/include mylib.h

Exemplo de Tradução

Código C:

// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

typedef struct {
    double real;
    double imag;
} Complex;

Complex complex_add(Complex a, Complex b);
Complex complex_mul(Complex a, Complex b);
double complex_magnitude(Complex c);

#endif

Após zig translate-c math_utils.h:

// math_utils.zig (gerado automaticamente)
pub const Complex = extern struct {
    real: f64,
    imag: f64,
};

pub extern fn complex_add(a: Complex, b: Complex) Complex;
pub extern fn complex_mul(a: Complex, b: Complex) Complex;
pub extern fn complex_magnitude(c: Complex) f64;

Integrando Código Traduzido

// Em vez de @cImport, use @import no arquivo traduzido
const math = @import("math_utils.zig");

pub fn main() void {
    const a = math.Complex{ .real = 1.0, .imag = 2.0 };
    const b = math.Complex{ .real = 3.0, .imag = 4.0 };
    
    const sum = math.complex_add(a, b);
    std.debug.print("Soma: {} + {}i\n", .{ sum.real, sum.imag });
    
    const mag = math.complex_magnitude(a);
    std.debug.print("Magnitude: {}\n", .{mag});
}

Compilando Código C com Zig

Usando zig cc

Zig pode substituir completamente gcc/clang:

# Compilar arquivo C
zig cc main.c -o programa

# Compilar múltiplos arquivos
zig cc main.c utils.c -o programa -I./include -L./lib -lmylib

# Com otimização
zig cc -O2 main.c -o programa

# Com símbolos de debug
zig cc -g main.c -o programa

Cross-compilation Fácil

# Compilar para Windows a partir do Linux/macOS
zig cc -target x86_64-windows-gnu main.c -o programa.exe

# Compilar para ARM Linux
zig cc -target aarch64-linux-gnu main.c -o programa_arm

# Compilar para WebAssembly
zig cc -target wasm32-wasi main.c -o programa.wasm

# Compilar para macOS a partir do Linux
zig cc -target x86_64-macos-none main.c -o programa_mac

Integrando C no build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "mixed_project",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // Adicionar código C ao projeto
    exe.addCSourceFile(.{
        .file = b.path("src/legacy.c"),
        .flags = &.{"-std=c11", "-Wall"},
    });
    
    exe.addCSourceFile(.{
        .file = b.path("src/utils.c"),
        .flags = &.{"-O2"},
    });

    // Headers
    exe.addIncludePath(b.path("include/"));
    
    exe.linkLibC();
    b.installArtifact(exe);
}

Projeto Prático: Wrapper de Biblioteca C

Vamos criar um wrapper completo para uma biblioteca C hipotética de logging:

Biblioteca C (clog.h / clog.c)

// clog.h
#ifndef CLOG_H
#define CLOG_H

typedef enum {
    LOG_DEBUG = 0,
    LOG_INFO = 1,
    LOG_WARN = 2,
    LOG_ERROR = 3
} LogLevel;

typedef struct Logger Logger;

Logger* logger_new(const char* name, LogLevel min_level);
void logger_free(Logger* logger);
void logger_log(Logger* logger, LogLevel level, const char* message);
void logger_set_level(Logger* logger, LogLevel level);

#endif

Wrapper Zig Idiomático

// src/clog.zig
const std = @import("std");
const c = @cImport(@cInclude("clog.h"));

pub const Level = enum(c_int) {
    debug = c.LOG_DEBUG,
    info = c.LOG_INFO,
    warn = c.LOG_WARN,
    error = c.LOG_ERROR,
};

pub const Logger = struct {
    raw: *c.Logger,
    name: []const u8,

    const Self = @This();

    pub fn new(name: []const u8, min_level: Level) !Self {
        const c_name = @ptrCast([*c]const u8, name.ptr);
        const raw = c.logger_new(c_name, @intFromEnum(min_level));
        
        if (raw == null) return error.OutOfMemory;
        
        return Self{
            .raw = raw.?,  // Unwrap optional
            .name = name,
        };
    }

    pub fn deinit(self: Self) void {
        c.logger_free(self.raw);
    }

    pub fn log(self: Self, level: Level, comptime fmt: []const u8, args: anytype) void {
        var buf: [1024]u8 = undefined;
        const message = std.fmt.bufPrint(&buf, fmt, args) catch |err| {
            std.debug.print("Erro ao formatar log: {}\n", .{err});
            return;
        };
        
        const c_msg = @ptrCast([*c]const u8, message.ptr);
        c.logger_log(self.raw, @intFromEnum(level), c_msg);
    }

    pub fn debug(self: Self, comptime fmt: []const u8, args: anytype) void {
        self.log(.debug, fmt, args);
    }

    pub fn info(self: Self, comptime fmt: []const u8, args: anytype) void {
        self.log(.info, fmt, args);
    }

    pub fn warn(self: Self, comptime fmt: []const u8, args: anytype) void {
        self.log(.warn, fmt, args);
    }

    pub fn err(self: Self, comptime fmt: []const u8, args: anytype) void {
        self.log(.error, fmt, args);
    }

    pub fn setLevel(self: Self, level: Level) void {
        c.logger_set_level(self.raw, @intFromEnum(level));
    }
};

Usando o Wrapper

// src/main.zig
const std = @import("std");
const clog = @import("clog.zig");

pub fn main() !void {
    // Criar logger (wrapper idiomático)
    const logger = try clog.Logger.new("MyApp", .info);
    defer logger.deinit();

    // Usar com API Zig amigável
    logger.info("Aplicação iniciada", .{});
    logger.debug("Isso não vai aparecer (nível mínimo é info)", .{});
    
    const user_count = 42;
    logger.info("Usuários conectados: {}", .{user_count});
    
    // Mudar nível dinamicamente
    logger.setLevel(.debug);
    logger.debug("Agora debug aparece!", .{});
    
    // Error com formatação
    const filename = "dados.txt";
    logger.err("Falha ao abrir arquivo: {s}", .{filename});
}
// build.zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "clog_demo",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // Compilar código C da biblioteca
    exe.addCSourceFile(.{
        .file = b.path("vendor/clog.c"),
        .flags = &.{"-std=c99"},
    });
    
    exe.addIncludePath(b.path("vendor/"));
    exe.linkLibC();

    b.installArtifact(exe);
}

Debugging Problemas Comuns

Problema: “undefined reference”

Causa: Biblioteca não linkada ou símbolo não encontrado.

Solução:

// Verifique se adicionou:
exe.linkSystemLibrary("nome_da_lib"); // sem o 'lib' prefixo
exe.addLibraryPath(b.path("caminho/para/libs/"));

Problema: “file not found” ao importar header

Causa: Include path não configurado.

Solução:

exe.addIncludePath(b.path("include/"));
// ou caminho absoluto do sistema
exe.addIncludePath(.{ .cwd_relative = "/usr/local/include" });

Problema: Tipos C não reconhecidos

Causa: Header depende de outros headers.

Solução:

const c = @cImport({
    @cDefine("_GNU_SOURCE", "1");  // Defines necessários
    @cInclude("stdlib.h");         // Headers que o header depende
    @cInclude("minha_lib.h");
});

Problema: Stack overflow com structs grandes

Causa: Struct C alocada na stack é muito grande.

Solução:

// ❌ Pode causar stack overflow
const big_struct: c.BigStruct = undefined;

// ✅ Alocar no heap
const big_struct = try allocator.create(c.BigStruct);
defer allocator.destroy(big_struct);

Problema: Segfault ao acessar ponteiro C

Causa: Ponteiro nulo não verificado.

Solução:

const ptr = c.some_function();
if (ptr == null) {
    return error.NullPointer;
}
// Agora seguro usar ptr.?

Checklist para Projetos Zig+C

Antes de iniciar um projeto misto:

  • Instalar bibliotecas C de desenvolvimento (libsqlite3-dev, etc.)
  • Configurar build.zig com addIncludePath e linkSystemLibrary
  • Testar @cImport com headers simples primeiro
  • Verificar conversões de tipo (especialmente strings)
  • Definir estratégia de gerenciamento de memória
  • Criar wrappers idiomáticos para APIs complexas
  • Documentar dependências C necessárias

Referência Rápida

Comandos Úteis

# Verificar quais bibliotecas estão instaladas
pkg-config --list-all

# Obter flags de compilação
pkg-config --cflags --libs sqlite3

# Traduzir header
zig translate-c -I/usr/include minha_lib.h > minha_lib.zig

# Compilar C com Zig
zig cc -c arquivo.c -o arquivo.o

# Ver símbolos em uma biblioteca
nm -D libminha_lib.so

Macros Comuns no build.zig

// Adicionar pkg-config automaticamente
const std = @import("std");

fn addPkgConfig(exe: *std.Build.Step.Compile, lib_name: []const u8) !void {
    // Em um script real, você usaria std.process.Child para executar pkg-config
    // e adicionar as flags automaticamente
}

Próximos Passos

Agora que você domina a interoperabilidade Zig-C, explore:

  1. Como Instalar o Zig — Se ainda não configurou seu ambiente
  2. Zig para Programadores C — Guia completo de migração
  3. Gerenciamento de Memória em Zig — Allocators e estratégias de memória
  4. Zig Build System — Domine o build.zig

Tem uma biblioteca C específica que quer usar com Zig? Entre em contato com a comunidade Zig Brasil — estamos sempre ajudando integradores!

Continue aprendendo Zig

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