Perguntas de Entrevista sobre Interoperabilidade C em Zig

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 CTipo Zig
intc_int
char*[*c]u8
void*?*anyopaque
size_tusize
NULLnull
struct XStruct 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. Use std.mem.span para 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.

  1. Comece linkando o código C existente com o build system Zig
  2. Escreva testes para módulos C existentes usando std.testing de Zig
  3. Migre módulos isolados um por um, começando pelos menores
  4. Use @cImport para que o novo código Zig chame o C restante
  5. Exporte funções Zig para que o código C possa chamar Zig
  6. 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.

Continue aprendendo Zig

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