Perguntas de Entrevista sobre Interoperabilidade C em Zig
A interoperabilidade com C é um dos pilares de Zig. A capacidade de importar headers C, chamar funções C sem overhead e linkar com bibliotecas C existentes torna Zig uma escolha prática para migração gradual de codebases C e integração com o vasto ecossistema de bibliotecas C existentes. Entrevistadores testam esse conhecimento especialmente quando a empresa possui código legado em C.
Fundamentos
Como importar e usar uma biblioteca C em Zig?
Zig pode importar headers C diretamente com @cImport:
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
});
pub fn main() void {
_ = c.printf("Hello from C!\n");
const ptr = c.malloc(100);
if (ptr) |p| {
c.free(p);
}
}
O compilador Zig traduz os tipos C para tipos Zig automaticamente.
Qual a diferença entre @cImport e usar o Zig build system para C?
@cImport traduz headers em tempo de compilação. O build system permite compilar arquivos .c junto com Zig, linkar bibliotecas estáticas/dinâmicas e gerenciar flags de compilação. Em projetos reais, ambos são usados juntos — veja build system.
Como os tipos C são mapeados para Zig?
| Tipo C | Tipo Zig |
|---|---|
int | c_int |
char* | [*c]u8 |
void* | ?*anyopaque |
size_t | usize |
NULL | null |
struct X | Struct Zig equivalente |
Ponteiros C ([*c]T) permitem aritmética de ponteiros e podem ser nulos, diferente de ponteiros Zig regulares.
Como chamar Zig a partir de C?
Exporte funções Zig com export:
export fn somar(a: c_int, b: c_int) c_int {
return a + b;
}
A função fica disponível para linkagem com código C usando o nome somar.
Perguntas Avançadas
Quais são os desafios comuns de interop C/Zig?
- Gerenciamento de memória misto: Código C usa malloc/free, código Zig usa allocators. É essencial manter consistência sobre quem aloca e quem libera.
- Strings: Strings C são null-terminated (
[*:0]const u8), slices Zig não são. Usestd.mem.spanpara converter. - Error handling: C usa códigos de retorno, Zig usa error unions. Wrappers devem traduzir entre os dois.
- Callbacks: Function pointers C podem não ser compatíveis diretamente com closures Zig (que capturam contexto).
Descreva uma estratégia de migração gradual de C para Zig.
- Comece linkando o código C existente com o build system Zig
- Escreva testes para módulos C existentes usando
std.testingde Zig - Migre módulos isolados um por um, começando pelos menores
- Use
@cImportpara que o novo código Zig chame o C restante - Exporte funções Zig para que o código C possa chamar Zig
- Gradualmente substitua até o código inteiro ser Zig
Essa abordagem é usada por empresas como Uber e Cloudflare. Veja também transição de C para Zig.
Como escrever um wrapper Zig idiomático para uma biblioteca C?
O padrão recomendado é criar um módulo Zig que encapsula a API C, traduzindo tipos e erros para idiomas Zig:
const c = @cImport(@cInclude("sqlite3.h"));
pub const Database = struct {
db: *c.sqlite3,
pub fn open(path: [:0]const u8) !Database {
var db: ?*c.sqlite3 = null;
const rc = c.sqlite3_open(path.ptr, &db);
if (rc != c.SQLITE_OK) return error.DatabaseOpenFailed;
return .{ .db = db.? };
}
pub fn close(self: *Database) void {
_ = c.sqlite3_close(self.db);
}
pub fn exec(self: *Database, sql: [:0]const u8) !void {
var errmsg: [*c]u8 = null;
const rc = c.sqlite3_exec(self.db, sql.ptr, null, null, &errmsg);
if (rc != c.SQLITE_OK) {
if (errmsg) |msg| c.sqlite3_free(msg);
return error.QueryFailed;
}
}
};
Observe os padrões: strings C usam [:0]const u8 (null-terminated), erros C são traduzidos para error unions Zig, e o gerenciamento de memória fica explícito. O chamador usa a API como se fosse uma biblioteca Zig nativa, sem precisar conhecer os detalhes de C.
Como usar translate-c para migração automática de código C?
O comando zig translate-c converte arquivos C para Zig automaticamente:
zig translate-c arquivo.c > arquivo_traduzido.zig
O resultado é um bom ponto de partida para migração, mas requer ajustes manuais: ponteiros [*c]T precisam ser convertidos para slices ou ponteiros Zig mais apropriados, macros C que @cImport não consegue traduzir precisam de equivalentes Zig, e o estilo de error handling precisa ser adaptado. Use translate-c como ferramenta de apoio, não como solução definitiva.
Quais cuidados tomar com callbacks C em Zig?
Callbacks em C são function pointers simples sem captura de contexto. Em Zig, closures capturam contexto, o que as torna incompatíveis com callbacks C diretamente. A solução é o padrão “userdata pointer”:
// A biblioteca C espera: void callback(void* userdata, int evento)
const MeuContexto = struct { contador: u32, limite: u32 };
fn meuCallback(userdata: ?*anyopaque, evento: c_int) callconv(.C) void {
const ctx: *MeuContexto = @ptrCast(@alignCast(userdata));
ctx.contador += 1;
if (ctx.contador >= ctx.limite) {
// lógica dependente de contexto
}
}
// Registro do callback com userdata
var contexto = MeuContexto{ .contador = 0, .limite = 10 };
c.registrar_callback(meuCallback, &contexto);
O callconv(.C) é obrigatório para garantir que a função use a convenção de chamada C (cdecl) e seja compatível com ponteiros de função C. Esse padrão é a forma idiomática de lidar com callbacks em bibliotecas C a partir de Zig.
Complemente com perguntas de build system e explore o ecossistema de bibliotecas. Pratique com tutoriais e projetos que integram C e Zig.