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:
@cInclude("stdio.h")diz ao compilador para processar o header C@cImportconverte as definições C em declarações Zigcse 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:
int→c_intchar*→[*c]u8(ponteiro C para u8)double→f64void*→*anyopaque
Tipos C em Zig
Tabela de Conversão de Tipos
Ao importar código C, Zig converte os tipos automaticamente:
| Tipo C | Tipo Zig | Observação |
|---|---|---|
char | c_char | Pode ser i8 ou u8 dependendo da plataforma |
short | c_short | Geralmente i16 |
int | c_int | Geralmente i32 |
long | c_long | Depende da plataforma (i32 ou i64) |
long long | c_longlong | Geralmente i64 |
float | f32 | Ponto flutuante 32-bit |
double | f64 | Ponto flutuante 64-bit |
void* | *anyopaque | Ponteiro para tipo opaco |
char* | [*c]u8 | Ponteiro C nulo-terminado |
const char* | [*c]const u8 | Ponteiro C constante |
size_t | c_size_t | Tipo de tamanho da plataforma |
FILE* | *c.FILE | Ponteiro para struct FILE |
struct X | c.struct_X | Struct com prefixo |
enum X | c.enum_X | Enum com prefixo |
union X | c.union_X | Union com prefixo |
typedef | Preservado | O 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
- Quem aloca, libera: Se C alocou com
malloc, libere comfree - Se Zig alocou, Zig libera: Use o mesmo allocator
- Não misture allocators: Não passe memória Zig para
freeC
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.zigcomaddIncludePathelinkSystemLibrary - Testar
@cImportcom 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:
- Como Instalar o Zig — Se ainda não configurou seu ambiente
- Zig para Programadores C — Guia completo de migração
- Gerenciamento de Memória em Zig — Allocators e estratégias de memória
- 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!