C can call Zig when the Zig function is exported with a C-compatible ABI.
The Zig function:
export fn add(a: c_int, b: c_int) c_int {
return a + b;
}export makes the symbol visible outside the Zig object file. The parameter and return types use C-compatible integer types, so C can call the function safely.
Put this in math.zig:
export fn add(a: c_int, b: c_int) c_int {
return a + b;
}Now write a C program:
#include <stdio.h>
int add(int a, int b);
int main(void) {
printf("%d\n", add(3, 4));
return 0;
}Build them together:
zig cc main.c math.zig -o mainRun it:
./mainThe output is:
7The C declaration:
int add(int a, int b);must match the Zig declaration:
export fn add(a: c_int, b: c_int) c_intThe names must match. The argument types must match. The return type must match.
For simple C types, use Zig’s C aliases:
c_char
c_short
c_int
c_long
c_longlong
c_uint
c_ulongFor pointers, export ordinary pointer-shaped data, not Zig-only structures.
This Zig function accepts a C string:
const std = @import("std");
export fn length(s: [*:0]const u8) c_int {
const n = std.mem.len(s);
return @intCast(n);
}C can call it:
#include <stdio.h>
int length(const char *s);
int main(void) {
printf("%d\n", length("hello"));
return 0;
}A C string is not a Zig slice. It is a pointer to bytes ending in zero. That is why the Zig type is:
[*:0]const u8Do not export a function like this to C:
export fn bad(s: []const u8) void {
_ = s;
}A Zig slice is two machine words: pointer and length. C does not know that type unless you define the same layout manually.
Use an explicit C-shaped interface instead:
export fn write_bytes(ptr: [*]const u8, len: usize) void {
const bytes = ptr[0..len];
_ = bytes;
}C calls it as:
void write_bytes(const unsigned char *ptr, size_t len);Structs can cross the boundary when their layout is fixed.
const Point = extern struct {
x: c_int,
y: c_int,
};
export fn sum_point(p: Point) c_int {
return p.x + p.y;
}The matching C code:
struct Point {
int x;
int y;
};
int sum_point(struct Point p);The keyword extern struct tells Zig to use C layout rules. A normal Zig struct has layout chosen by Zig and must not be treated as a C struct.
The same rule applies to enums and unions. Use C-shaped declarations at the boundary. Convert to better Zig types inside the Zig code.
A common pattern is:
export fn api_function(raw_ptr: ?*anyopaque) c_int {
if (raw_ptr == null) return -1;
// Convert raw C-shaped inputs here.
// Then call ordinary Zig code.
return 0;
}The exported layer should be small. It should check inputs, convert C data into Zig data, and return C-compatible results.
The rest of the program can remain ordinary Zig.