@cImport / @cInclude em Zig
O @cImport importa cabeçalhos C diretamente para Zig, permitindo chamar funções C sem escrever bindings manuais. Dentro do bloco @cImport, use @cInclude para incluir headers, @cDefine para definir macros e @cUndef para desdefini-las. O compilador Zig traduz automaticamente as declarações C para tipos Zig equivalentes.
Sintaxe
@cImport(comptime expr: void) type
@cInclude(comptime path: []const u8) void
@cDefine(comptime name: []const u8, comptime value: []const u8) void
@cUndef(comptime name: []const u8) void
Parâmetros
- @cImport: Recebe um bloco de expressões comptime que configuram quais headers importar.
- @cInclude: Caminho do header C a incluir (como
#includedo C). - @cDefine: Define uma macro (como
#definedo C). - @cUndef: Remove definição de macro.
Valor de retorno
@cImport retorna um type — a struct que contém todas as declarações públicas traduzidas dos headers C.
Exemplos práticos
Exemplo 1: Importar a biblioteca C padrão
const std = @import("std");
const c = @cImport(@cInclude("stdio.h"));
pub fn main() void {
_ = c.printf("Hello from C! %d\n", @as(c_int, 42));
}
Exemplo 2: Usar math.h
const std = @import("std");
const c = @cImport({
@cInclude("math.h");
@cInclude("stdlib.h");
});
pub fn main() void {
const raiz = c.sqrt(144.0);
std.debug.print("sqrt(144) = {d}\n", .{raiz}); // 12.0
const absoluto = c.abs(-42);
std.debug.print("abs(-42) = {}\n", .{absoluto}); // 42
}
Exemplo 3: Importar biblioteca externa com defines
const c = @cImport({
@cDefine("NCURSES_WIDECHAR", "1");
@cInclude("ncurses.h");
});
pub fn main() void {
_ = c.initscr();
_ = c.printw("Hello, ncurses from Zig!\n");
_ = c.refresh();
_ = c.getch();
_ = c.endwin();
}
Configuração no build.zig
Para usar @cImport com bibliotecas externas, configure os caminhos 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 = "app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Linkar com biblioteca C
exe.linkLibC();
exe.linkSystemLibrary("ncurses");
// Adicionar caminhos de include se necessário
exe.addIncludePath(b.path("include"));
b.installArtifact(exe);
}
Funções Auxiliares
| Função | Descrição |
|---|---|
@cImport(...) | Iniciar bloco de importação C |
@cInclude("header.h") | Incluir header (como #include) |
@cDefine("MACRO", "valor") | Definir macro de pré-processador |
@cUndef("MACRO") | Remover definição de macro |
Casos de uso comuns
- Bibliotecas C existentes: Usar SQLite, OpenSSL, SDL, etc. diretamente de Zig.
- APIs do sistema: Acessar syscalls e interfaces do OS.
- Migração gradual: Chamar código C legado enquanto migra para Zig.
- Protótipos rápidos: Usar bibliotecas C sem escrever wrappers manuais.
Como o @cImport funciona internamente
O Zig invoca o Clang como biblioteca para parsear os cabeçalhos C e gera um módulo Zig equivalente. Esse processo acontece em tempo de compilação. O módulo gerado fica em cache e não precisa ser reprocessado a cada compilação se os headers não mudarem.
Tipos C são mapeados para tipos Zig equivalentes:
| Tipo C | Tipo Zig |
|---|---|
int | c_int |
unsigned int | c_uint |
long | c_long |
char * | [*c]u8 |
void * | ?*anyopaque |
size_t | usize |
bool (C99) | bool |
| ponteiro de função | ponteiro de função Zig |
Lidando com ponteiros C (*[c])
O tipo [*c]T representa ponteiros C — que podem ser null, unitários ou apontar para arrays. Ao usar funções C que retornam esses ponteiros, verifique null antes de usar:
const c = @cImport(@cInclude("string.h"));
pub fn main() !void {
const str = "hello world";
const pos = c.strchr(str.ptr, 'w');
if (pos == null) {
return error.NaoEncontrado;
}
// pos é [*c]u8, não null, usar com cuidado
const resultado: []const u8 = std.mem.span(pos);
_ = resultado;
}
Macros C e limitações
@cImport lida com funções C e constantes, mas macros C complexas não são traduzidas automaticamente. Macros simples que definem constantes funcionam. Macros que são pseudo-funções frequentemente falham:
// Isso funciona: macro que define constante
// #define MAX_SIZE 1024
// -> c.MAX_SIZE == 1024
// Isso pode não funcionar: macro-função
// #define MIN(a, b) ((a) < (b) ? (a) : (b))
// -> c.MIN pode não existir
// Solução: reescrever em Zig
fn min(a: anytype, b: anytype) @TypeOf(a, b) {
return if (a < b) a else b;
}
Use zig translate-c header.h na linha de comando para ver como o Zig traduz um header e identificar o que está disponível.
Boas práticas
Isolar o @cImport em um único arquivo: Em vez de espalhar @cImport pelo código, crie um arquivo c.zig com todas as importações C e importe esse módulo nos outros arquivos. Isso facilita manutenção e compilação incremental.
// c.zig
pub const c = @cImport({
@cInclude("sqlite3.h");
@cInclude("openssl/ssl.h");
});
// main.zig
const c = @import("c.zig").c;
Erros comuns
Esquecer de linkar a biblioteca: @cImport resolve os tipos, mas você ainda precisa de exe.linkSystemLibrary("nome") no build.zig para que o linker encontre as implementações.
Usar ponteiros C sem verificar null: Funções C retornam [*c]T ou ?*T que podem ser null. Verifique sempre antes de dereferenciar.
Perguntas Frequentes
Posso usar @cImport com headers locais do meu projeto?
Sim. Use exe.addIncludePath(b.path("include")) no build.zig para adicionar diretórios de include, e então @cInclude("meu_header.h") funcionará normalmente.
@cImport é mais lento que bindings manuais?
Em termos de performance do binário gerado, não há diferença — o código C compilado é o mesmo. O tempo de compilação pode ser um pouco maior na primeira vez, mas o resultado é cacheado.
Builtins relacionados
- @import — Importar módulos Zig
- @embedFile — Incorporar arquivos no binário
- @ptrCast — Converter ponteiros entre tipos Zig e C