Décadas de trabajo en C no van a desaparecer. Hay miles de bibliotecas, drivers y sistemas escritos en C que funcionan perfectamente y nadie va a reescribir. Zig lo sabe, y por eso el interop con C no es una característica adicional que requiera bindings ni herramientas externas: está en el núcleo del lenguaje.
@cImport y @cInclude: incluir cabeceras C directamente
En Zig puedes incluir cabeceras C directamente con @cImport y @cInclude. El compilador parsea la cabecera C y traduce las declaraciones a tipos Zig. No hay fichero de bindings generado, no hay herramienta intermedia.
const std = @import("std");
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
@cInclude("string.h");
});
pub fn main() void {
// Llamar a printf de la libc directamente
_ = c.printf("Hola desde C: %dn", 42);
// Usar malloc y free de la libc
const ptr = c.malloc(1024) orelse {
std.debug.print("malloc fallón", .{});
return;
};
defer c.free(ptr);
// strlen también funciona
const texto = "hola mundo";
const len = c.strlen(texto);
std.debug.print("Longitud: {d}n", .{len});
}
El compilador Zig incluye Clang internamente, así que parsear cabeceras C es algo que hace de forma nativa sin depender de ninguna instalación externa.
Usar SQLite desde Zig
Un ejemplo más realista: usar SQLite, una de las bibliotecas C más usadas del mundo.
const std = @import("std");
const c = @cImport({
@cInclude("sqlite3.h");
});
pub fn main() !void {
var db: ?*c.sqlite3 = null;
const rc = c.sqlite3_open(":memory:", &db);
if (rc != c.SQLITE_OK) {
std.debug.print("Error al abrir: {s}n", .{c.sqlite3_errmsg(db)});
return error.SqliteError;
}
defer _ = c.sqlite3_close(db);
var errmsg: [*c]u8 = null;
const sql = "CREATE TABLE test (id INTEGER PRIMARY KEY, nombre TEXT)";
const rc2 = c.sqlite3_exec(db, sql, null, null, &errmsg);
if (rc2 != c.SQLITE_OK) {
std.debug.print("Error SQL: {s}n", .{errmsg});
c.sqlite3_free(errmsg);
return error.SqliteError;
}
std.debug.print("Tabla creada correctamenten", .{});
}
zig cc: el compilador C de Zig
Zig incluye un compilador C completo (basado en Clang) accesible como zig cc. Puedes usarlo como reemplazo de gcc o clang para compilar código C existente, y la gran ventaja es que soporta cross-compilation a cualquier target sin configuración adicional.
# Compilar un fichero C normal zig cc programa.c -o programa # Cross-compilar para ARM64 Linux zig cc programa.c -o programa-arm64 -target aarch64-linux-musl # Cross-compilar para Windows desde Linux zig cc programa.c -o programa.exe -target x86_64-windows-gnu
Esto es útil cuando tienes proyectos C existentes que quieres cross-compilar sin montar toolchains específicos. zig cc lleva consigo libc estática para todos los targets principales.
Incluir código fuente C en un proyecto Zig
Si tienes ficheros .c que quieres compilar junto con tu código Zig, el sistema de build permite añadirlos directamente:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "mi-programa",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.addCSourceFiles(.{
.files = &.{
"src/util.c",
"src/parser.c",
},
.flags = &.{"-std=c11", "-O2"},
});
exe.linkLibC();
b.installArtifact(exe);
}
Exportar funciones Zig para usar desde C
El interop funciona en los dos sentidos. Puedes escribir código Zig que C puede llamar marcando las funciones con export y la convención de llamada C:
// lib.zig - código Zig que se expone como biblioteca C
const std = @import("std");
export fn sumar(a: i32, b: i32) i32 {
return a + b;
}
export fn longitud_string(s: [*c]const u8) usize {
return std.mem.len(s);
}
En el lado C, la cabecera es simplemente:
// lib.h #pragma once #include <stdint.h> #include <stddef.h> int32_t sumar(int32_t a, int32_t b); size_t longitud_string(const char* s);
Tipos C en Zig
Zig tiene tipos específicos para la interoperabilidad con C. El tipo [*c]T representa un puntero C (que puede ser nulo y no tiene longitud implícita), distinto de los slices y punteros seguros de Zig.
Tipo C | Equivalente Zig |
char * | [*c]u8 |
const char * | [*c]const u8 |
void * | *anyopaque |
int | c_int |
long | c_long |
size_t | usize |
La interop con C es una de las razones por las que Zig se usa mucho como reemplazo de C en proyectos existentes: puedes migrar fichero a fichero sin reescribir todo de golpe. Proyectos como Bun aprovechan exactamente esto: Zig compila junto con código C/C++ del motor JavaScriptCore sin necesidad de capas de bindings adicionales.
Imagen: Pexels / Ali Haider
