A C program and a Zig program can share data only when both sides agree on layout.
Layout means the exact order, size, alignment, and padding of values in memory. C code does not see Zig types. It sees bytes at addresses. If the bytes do not have the shape C expects, the program is wrong.
For simple integer and floating-point values, use C-compatible types:
c_char
c_short
c_int
c_long
c_longlong
c_float
c_doubleThese names follow the target C ABI.
For structs, use extern struct.
const Point = extern struct {
x: c_int,
y: c_int,
};This matches the usual C declaration:
struct Point {
int x;
int y;
};A normal Zig struct should not be passed to C as a C struct.
const BadPoint = struct {
x: i32,
y: i32,
};Zig may choose a layout that is useful for Zig. C cannot rely on it.
The same rule applies to unions.
const Number = extern union {
i: c_int,
d: c_double,
};This matches:
union Number {
int i;
double d;
};For packed binary formats, use packed struct, but do not confuse it with C layout. Packed layout is for bit-level representation. C struct layout is ABI layout.
const Flags = packed struct {
read: bool,
write: bool,
exec: bool,
};This is useful for exact bits. It is not a general replacement for extern struct.
Function layout is controlled by the calling convention.
extern "c" fn puts(s: [*:0]const u8) c_int;The "c" convention tells Zig to call the function as C would call it on the target platform.
When exporting a function to C, keep the boundary plain:
export fn scale(x: c_double, factor: c_double) c_double {
return x * factor;
}Avoid Zig-only parameter types in exported functions:
export fn bad(xs: []const u8) void {
_ = xs;
}A slice has Zig layout. C does not know it. Use pointer plus length:
export fn good(ptr: [*]const u8, len: usize) void {
const xs = ptr[0..len];
_ = xs;
}At the boundary, prefer boring types.
Inside Zig, convert them into safer Zig forms. Keep C layout at the edge.