One of Zig’s design goals is direct interoperability with C.
A Zig program can call C functions without wrapper generators, external build tools, or foreign function interfaces layered on top of the language. Zig understands C calling conventions, C data layout, and C header files directly.
A small example is enough to begin.
Suppose we have this C source file:
#include <stdio.h>
void greet(const char *name) {
printf("hello, %s\n", name);
}Save it as greet.c.
Now write a Zig program that calls it:
const std = @import("std");
extern fn greet(name: [*:0]const u8) void;
pub fn main() void {
greet("zig");
}Build and run:
zig run main.zig greet.cThe output is:
hello, zigThe important line is the declaration:
extern fn greet(name: [*:0]const u8) void;extern says the function is defined outside Zig.
The declaration gives Zig enough information to call the function correctly:
- the function name
- the parameter types
- the return type
- the calling convention
The parameter type:
[*:0]const u8is a many-item pointer terminated by zero. This matches a C string:
const char *Zig does not have a separate built-in string type compatible with C. Instead, the programmer states the exact memory representation.
A Zig string literal:
"zig"already contains a trailing zero byte when needed for C interoperation, so it can be passed directly here.
C functions that return integers work naturally.
This C source:
int add(int a, int b) {
return a + b;
}can be called from Zig:
const std = @import("std");
extern fn add(a: c_int, b: c_int) c_int;
pub fn main() void {
const result = add(3, 4);
std.debug.print("{d}\n", .{result});
}Run it:
zig run main.zig add.cThe output is:
7Notice the type:
c_intZig provides C-compatible integer aliases:
| Zig type | C type |
|---|---|
c_char | char |
c_short | short |
c_int | int |
c_long | long |
c_longlong | long long |
These types match the target platform’s C ABI.
C pointers map naturally into Zig pointer types.
| C | Zig |
|---|---|
int * | *c_int |
const char * | [*:0]const u8 |
void * | *anyopaque |
Zig distinguishes several pointer kinds because the language tries to make memory usage explicit.
A normal Zig slice:
[]const u8contains:
- a pointer
- a length
A C string does not contain a length. It depends on a terminating zero byte instead. For this reason, Zig uses different types for the two representations.
C functions may also be declared with explicit calling conventions:
extern "c" fn puts(s: [*:0]const u8) c_int;The "c" calling convention matches the platform ABI used by C compilers.
In practice, ordinary extern fn declarations already default to the C ABI on supported targets.
A Zig program may link against:
- standalone C source files
- static libraries
- shared libraries
- operating system APIs
For example:
zig build-exe main.zig greet.cor:
zig build-exe main.zig -lcto link against the system C library.
The reverse direction is also possible: C code can call Zig functions.
A Zig function exported to C looks like this:
export fn square(x: c_int) c_int {
return x * x;
}export makes the symbol visible to external code.
Zig tries to make interoperability ordinary rather than exceptional. A C function declaration in Zig looks close to its original form, and the generated machine code follows the same ABI rules as a C compiler.