@cImport does two jobs.
It asks the C compiler front end to read a header, and it asks Zig to translate the visible C declarations into Zig declarations.
For this C header:
#ifndef MATHLIB_H
#define MATHLIB_H
int add(int a, int b);
double mean(double a, double b);
#endifZig can import it:
const c = @cImport({
@cInclude("mathlib.h");
});The declarations are then used through c:
const std = @import("std");
const c = @cImport({
@cInclude("mathlib.h");
});
pub fn main() void {
const x = c.add(2, 5);
const y = c.mean(10.0, 20.0);
std.debug.print("{d} {d}\n", .{ x, y });
}Build it with the header path and C source:
zig run main.zig mathlib.c -I.A function prototype in C becomes an external function declaration in Zig.
int add(int a, int b);is translated as a function that can be called as:
c.add(2, 5)A C struct becomes a Zig extern struct.
struct Point {
int x;
int y;
};It can be used from Zig:
const c = @cImport({
@cInclude("point.h");
});
pub fn main() void {
var p = c.struct_Point{
.x = 10,
.y = 20,
};
_ = p;
}The exact translated name depends on the C declaration. Named C structs commonly appear with names such as struct_Point.
A C typedef gives a more pleasant name.
typedef struct {
int x;
int y;
} Point;Then Zig code can use:
var p = c.Point{
.x = 10,
.y = 20,
};This is usually the better C header style for Zig users.
Macros are more limited.
Simple numeric macros translate well:
#define MAX_COUNT 1024can be used as:
const n = c.MAX_COUNT;Function-like macros may or may not translate. Zig can translate some simple macros, but C macros are token substitution, not typed declarations. Complex macros should be treated as C code, not as a clean Zig interface.
For example, this is clear in C:
#define SQUARE(x) ((x) * (x))But in Zig it is often better to write a Zig function:
fn square(x: i32) i32 {
return x * x;
}C enums are translated into Zig values whose exact shape follows the C ABI. Do not assume they behave like ordinary Zig enum types unless you check the translated declaration.
C pointers also keep their C nature. A declaration such as:
const char *name(void);is best treated in Zig as a pointer to zero-terminated bytes:
const s = c.name();Before using it as a Zig slice, measure its length or convert it carefully.
The rule is simple: translated headers preserve the C interface. They do not turn C into idiomatic Zig.
For a large library, this is an advantage. The header remains the source of truth. Zig tracks the ABI. The Zig program decides where to add a safer, clearer wrapper.