C Interoperability
Zig provides first-class C interoperability, allowing seamless integration with existing C code and libraries.
Overview
Zig can:
Call C functions directly
Use C types and structs
Link against C libraries
Import C header files
Act as a C compiler (zig cc)
Translate C code to Zig
Using C Functions
Linking libc
--libc [file] # Specify libc paths file
-lc # Link against system libc
zig build-exe -lc main.zig
const exe = b . addExecutable (.{
. name = "myapp" ,
. root_source_file = .{ . path = "src/main.zig" },
. target = target ,
. optimize = optimize ,
});
exe . linkLibC ();
const c = @cImport ({
@cInclude ( "stdio.h" );
@cInclude ( "stdlib.h" );
@cInclude ( "math.h" );
});
pub fn main () void {
_ = c . printf ( "Hello from C! \n " );
const result = c . sqrt ( 16.0 );
_ = c . printf ( "sqrt(16) = %f \n " , result );
}
With Defines and Include Paths
With Defines
Include Paths
const c = @cImport ({
@cDefine ( "_GNU_SOURCE" , {});
@cDefine ( "DEBUG" , "1" );
@cInclude ( "features.h" );
});
C Types in Zig
Basic C Types
C Type Zig Type Description intc_intC integer unsigned intc_uintC unsigned integer longc_longC long unsigned longc_ulongC unsigned long long longc_longlongC long long shortc_shortC short charc_charC char floatf3232-bit float doublef6464-bit float void**anyopaqueOpaque pointer
Type Example
const std = @import ( "std" );
extern fn add ( a : c_int , b : c_int ) c_int ;
pub fn main () void {
const x : c_int = 10 ;
const y : c_int = 20 ;
const result = add ( x , y );
std . debug . print ( "{d} \n " , .{ result });
}
Calling C Functions
Extern Functions
C Code (mathlib.c)
Zig Code
Build
#include <math.h>
double calculate_distance ( double x1 , double y1 , double x2 , double y2 ) {
double dx = x2 - x1;
double dy = y2 - y1;
return sqrt (dx * dx + dy * dy);
}
Variadic Functions
const c = @cImport ({
@cInclude ( "stdio.h" );
});
pub fn main () void {
const name = "Alice" ;
const age : c_int = 30 ;
_ = c . printf ( "Name: %s, Age: %d \n " , name . ptr , age );
}
C Structs
Using C Structs
C Header (point.h)
C Implementation (point.c)
Zig Usage
Build
typedef struct {
double x;
double y;
} Point;
Point make_point ( double x , double y );
double point_distance (Point p1 , Point p2 );
Linking C Libraries
System Libraries
-l[lib] # Link library
-L[dir] # Add library search path
-I[dir] # Add include search path
Math Library
Custom Location
Multiple Libraries
zig build-exe -lc -lm main.zig
zig build-exe -lc -L/opt/mylib/lib -I/opt/mylib/include -lmylib main.zig
zig build-exe -lc -lm -lpthread -lssl -lcrypto main.zig
Using build.zig
System Library
Static Library
Include Paths
const exe = b . addExecutable (.{
. name = "myapp" ,
. root_source_file = .{ . path = "src/main.zig" },
. target = target ,
. optimize = optimize ,
});
exe . linkLibC ();
exe . linkSystemLibrary ( "ssl" );
exe . linkSystemLibrary ( "crypto" );
Compiling C Code
C Source Files
zig build-exe main.zig helper.c utils.c -lc
With Flags
-cflags [flags] -- # Set C compiler flags
zig build-exe main.zig file.c -lc -cflags -O2 -Wall -Wextra --
In build.zig
const exe = b . addExecutable (.{
. name = "myapp" ,
. root_source_file = .{ . path = "src/main.zig" },
. target = target ,
. optimize = optimize ,
});
exe . linkLibC ();
exe . addCSourceFile (.{
. file = .{ . path = "src/helper.c" },
. flags = & .{
"-Wall" ,
"-Wextra" ,
"-O2" ,
"-DDEBUG" ,
},
});
Translating C to Zig
zig translate-c
Convert C code to Zig:
Basic Translation
With Includes
With Defines
zig translate-c input.h > output.zig
Example Translation
The generated Zig code may need manual cleanup for idiomatic Zig patterns.
Zig as a C Compiler
zig cc
Use Zig as a drop-in C compiler:
Compile C
With Optimization
Cross-Compile
zig cc -o program main.c helper.c -lm
zig c++
Use Zig as a C++ compiler:
zig c++ -o program main.cpp -lstdc++
Benefits
Cross-Compilation Easily cross-compile C/C++ code without separate toolchains
Bundled Toolchain No need to install separate C compiler or libc
Target Support Support for many targets out of the box
Consistent Same compiler across all platforms
C String Handling
Null-Terminated Strings
Zig to C String
C to Zig String
const std = @import ( "std" );
const c = @cImport ({
@cInclude ( "stdio.h" );
});
pub fn main () void {
const zig_string = "Hello, World!" ;
const c_string : [ * : 0 ] const u8 = zig_string . ptr ;
_ = c . puts ( c_string );
}
String Literals
const c = @cImport ({
@cInclude ( "string.h" );
});
pub fn main () void {
const str1 = "Hello" ;
const str2 = "World" ;
const result = c . strcmp ( str1 . ptr , str2 . ptr );
std . debug . print ( "strcmp result: {d} \n " , .{ result });
}
Memory Management
Using C Allocators
malloc/free
C Allocator Wrapper
const c = @cImport ({
@cInclude ( "stdlib.h" );
});
pub fn main () void {
const ptr = c . malloc ( 1024 );
defer c . free ( ptr );
// Use allocated memory
const slice : [ * ] u8 = @ptrCast ( ptr );
slice [ 0 ] = 42 ;
}
Error Handling
C errno
const std = @import ( "std" );
const c = @cImport ({
@cInclude ( "errno.h" );
@cInclude ( "stdio.h" );
});
pub fn main () void {
const file = c . fopen ( "nonexistent.txt" , "r" );
if ( file == null ) {
const err = c . errno ;
std . debug . print ( "Error: {d} \n " , .{ err });
return ;
}
defer _ = c . fclose ( file );
}
Wrapping C Errors
const std = @import ( "std" );
const FileError = error {
NotFound ,
PermissionDenied ,
Unknown ,
};
extern fn c_open_file ( path : [ * : 0 ] const u8 ) ?* anyopaque ;
fn openFile ( path : [] const u8 ) FileError !* anyopaque {
const c_path = path . ptr ;
return c_open_file ( c_path ) orelse return FileError . NotFound ;
}
Callbacks
Function Pointers
C Header
Zig Implementation
typedef void ( * callback_t )( int value);
void register_callback ( callback_t cb );
void trigger_callback ();
Callbacks must use .C calling convention: callconv(.C)
Complete Example
C Library (mylib.h)
C Implementation (mylib.c)
Zig Usage (main.zig)
Build and Run
#ifndef MYLIB_H
#define MYLIB_H
typedef struct {
int width;
int height;
} Rectangle;
Rectangle make_rectangle ( int w , int h );
int rectangle_area (Rectangle rect );
void print_rectangle (Rectangle rect );
#endif
Common Patterns
Opaque Pointers
const FileHandle = opaque {};
extern fn open_file ( path : [ * : 0 ] const u8 ) ?* FileHandle ;
extern fn close_file ( handle : * FileHandle ) void ;
extern fn read_file ( handle : * FileHandle , buffer : [ * ] u8 , size : usize ) isize ;
pub fn main () ! void {
const handle = open_file ( "file.txt" ) orelse return error . FileNotFound ;
defer close_file ( handle );
var buffer : [ 1024 ] u8 = undefined ;
const bytes_read = read_file ( handle , & buffer , buffer . len );
// ...
}
Bitfields
const Flags = packed struct {
read : bool ,
write : bool ,
execute : bool ,
_padding : u29 = 0 ,
};
extern fn set_permissions ( flags : Flags ) void ;
Best Practices
Use @cImport - Let Zig translate C headers automatically
Link libc explicitly - Always add -lc when using C code
Check C function returns - C functions don’t use Zig’s error model
Use correct calling convention - Callbacks need callconv(.C)
Handle null pointers - C pointers can be null
Mind string encoding - C uses null-terminated strings
Test thoroughly - C interop can have subtle issues
Prefer Zig implementations - When possible, use native Zig code
Next Steps
CLI Reference Complete CLI flag reference
Build System Learn about build.zig