Cheatsheet: Interop com C em Zig

Cheatsheet: Interop com C em Zig

Uma das grandes forças de Zig é a integração nativa e sem fricção com código C. O compilador Zig inclui um compilador C (baseado em Clang), pode importar headers C diretamente, linkar com bibliotecas C e até compilar código C como parte do projeto — tudo sem ferramentas externas.

Importando Headers C

@cImport — Importar headers C

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

pub fn main() void {
    // Chamar printf do C
    _ = c.printf("Olá do C! %d\n", @as(c_int, 42));

    // malloc/free do C
    const ptr = c.malloc(100);
    if (ptr) |p| {
        defer c.free(p);
        // usar o ponteiro...
    }
}

Importar biblioteca de terceiros

const c = @cImport({
    @cDefine("_GNU_SOURCE", {});           // #define
    @cDefine("SDL_MAIN_HANDLED", {});
    @cInclude("SDL2/SDL.h");
});

pub fn main() !void {
    if (c.SDL_Init(c.SDL_INIT_VIDEO) != 0) {
        std.debug.print("Erro SDL: {s}\n", .{c.SDL_GetError()});
        return error.SDLInitFailed;
    }
    defer c.SDL_Quit();

    // Criar janela, etc.
}

Tradução de Tipos C → Zig

Tabela de equivalência

Tipo CTipo ZigNotas
charu8Em Zig, sempre unsigned
signed chari8
unsigned charu8
shortc_shortGeralmente i16
intc_intGeralmente i32
longc_longi32 ou i64 conforme plataforma
long longc_longlongGeralmente i64
unsigned intc_uint
size_tusize
floatf32
doublef64
void*?*anyopaquePonteiro opaco nullable
const char*[*:0]const u8String C terminada em null
int**c_int ou [*]c_intDepende do uso
NULLnull
bool (_Bool)bool
struct Xtraduzido automaticamente
enum Xtraduzido automaticamente

Conversões de ponteiros

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

pub fn main() void {
    // String Zig para C
    const zig_str: [:0]const u8 = "Olá";
    const c_str: [*:0]const u8 = zig_str.ptr;
    const len = c.strlen(c_str);
    std.debug.print("Tamanho: {d}\n", .{len});

    // Ponteiro C para slice Zig
    var buffer: [100]u8 = undefined;
    const c_ptr: [*]u8 = &buffer;
    const zig_slice: []u8 = c_ptr[0..50]; // criar slice com tamanho conhecido
    _ = zig_slice;

    // void* para tipo específico
    const void_ptr: ?*anyopaque = c.malloc(100);
    if (void_ptr) |p| {
        const typed_ptr: [*]u8 = @ptrCast(@alignCast(p));
        typed_ptr[0] = 42;
        c.free(p);
    }
}

Chamar Funções C a Partir de Zig

Exemplo com libc

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

pub fn main() void {
    // Funções matemáticas
    const raiz = c.sqrt(144.0);
    std.debug.print("Raiz de 144: {d}\n", .{raiz}); // 12.0

    const seno = c.sin(std.math.pi / 2.0);
    std.debug.print("sen(π/2): {d}\n", .{seno}); // 1.0

    // Tempo
    var agora: c.time_t = undefined;
    _ = c.time(&agora);
    const tm = c.localtime(&agora);
    if (tm) |t| {
        std.debug.print("Hora: {d}:{d:0>2}:{d:0>2}\n", .{
            t.*.tm_hour, t.*.tm_min, t.*.tm_sec,
        });
    }
}

Callbacks — Passar função Zig para C

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

fn comparar(a: ?*const anyopaque, b: ?*const anyopaque) callconv(.C) c_int {
    const val_a: *const i32 = @ptrCast(@alignCast(a));
    const val_b: *const i32 = @ptrCast(@alignCast(b));
    if (val_a.* < val_b.*) return -1;
    if (val_a.* > val_b.*) return 1;
    return 0;
}

pub fn main() void {
    var nums = [_]i32{ 5, 2, 8, 1, 9, 3 };

    // Usar qsort do C com callback Zig
    c.qsort(
        @ptrCast(&nums),
        nums.len,
        @sizeOf(i32),
        comparar,
    );

    // nums agora está ordenado: [1, 2, 3, 5, 8, 9]
    for (nums) |n| {
        std.debug.print("{d} ", .{n});
    }
}

Exportar Funções Zig para C

Criar biblioteca usável por C

// lib.zig — exportar funções para C
const std = @import("std");

export fn zig_somar(a: c_int, b: c_int) c_int {
    return a + b;
}

export fn zig_processar(dados: [*]u8, tamanho: usize) c_int {
    const slice = dados[0..tamanho];
    // processar dados...
    _ = slice;
    return 0; // sucesso
}

// Gerar header C automaticamente com:
// zig build-lib lib.zig -dynamic -femit-h

Header gerado (lib.h):

int zig_somar(int a, int b);
int zig_processar(unsigned char* dados, size_t tamanho);

Configuração no build.zig

Linkar com bibliotecas C

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

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

    // Linkar com libc
    exe.linkLibC();

    // Linkar com bibliotecas do sistema
    exe.linkSystemLibrary("SDL2");
    exe.linkSystemLibrary("openssl");
    exe.linkSystemLibrary("z"); // zlib

    // Caminhos de include
    exe.addIncludePath(b.path("include"));
    exe.addIncludePath(.{ .cwd_relative = "/usr/local/include" });

    // Caminhos de biblioteca
    exe.addLibraryPath(.{ .cwd_relative = "/usr/local/lib" });

    // Compilar arquivos C como parte do projeto
    exe.addCSourceFiles(.{
        .files = &.{
            "src/wrapper.c",
            "vendor/lib/util.c",
        },
        .flags = &.{
            "-Wall",
            "-O2",
            "-std=c11",
        },
    });

    b.installArtifact(exe);
}

Structs C em Zig

// Struct compatível com layout C
const Ponto = extern struct {
    x: f64,
    y: f64,
    z: f64,
};

// Packed struct (sem padding)
const Header = packed struct {
    versao: u4,
    tipo: u4,
    tamanho: u16,
    flags: u8,
};

// Usar struct C importada
const c = @cImport(@cInclude("minha_lib.h"));

pub fn main() void {
    var ponto = c.criar_ponto(1.0, 2.0, 3.0);
    c.mover_ponto(&ponto, 10.0, 0.0, 0.0);
}

Calling Conventions

// Padrão Zig (não compatível com C)
fn funcaoZig() void {}

// Convenção C — necessário para interop
fn funcaoC() callconv(.C) void {}

// Exportar para C
export fn exportada() c_int {
    return 0;
}

// Ponteiro de função com convenção C
const FnCallback = *const fn (c_int, c_int) callconv(.C) c_int;

Erros Comuns

// ERRO: Esquecer de linkar libc
// exe.linkLibC();  // necessário se usar @cImport

// ERRO: Tipo errado na conversão
// const x: i32 = valor_c;  // use c_int, não i32

// CORRETO
const x: c_int = valor_c;

// ERRO: String Zig não termina em null para C
// const s = "texto"; // pode não ter sentinela 0
// CORRETO: usar literal com sentinela
const s: [:0]const u8 = "texto"; // terminado em 0

Veja Também

Continue aprendendo Zig

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