Zig FFI: Calling Zig from C

Zig FFI: Calling Zig from C
The ultimate power of a systems language is its ability to "Play well with others." If you write a high-performance "Image Processor" in Zig, you don't want to rewrite it in Python just so a Python developer can use it.
You want to Export it. Zig allows you to create .dll (Windows) or .so (Linux) files that look exactly like C libraries. This 1,500+ word guide explores the export keyword, the extern struct, and the architecture of "Global Compatibility."
1. The export Keyword: Visibility
To make a function visible to the "Outside World" (C), you must use export.
Architecture Note: Notice that the function is Not pub. pub is for other Zig files. export is for the CPU's linker. When you use export, the name add is saved directly into the binary file's symbol table.
2. The Physics of the Export: Symbol Visibility and Linker Tables
When you export a function, you are talking to the Linker, not just the compiler.
The Symbol Mirror
- The Concept: Every binary file (like an
.soor.dll) has a Dynamic Symbol Table. - The Physics: By default, functions are "Hidden" within the binary to optimize file size.
exportflips a bit in the ELF or PE header, making the function's entry point address public. - The Result: When a Python script "Loads" your Zig library, it searches this table for the name
add. The linker then maps the Python function call directly to your Zig function's physical starting address in RAM, allowing for zero-latency execution.
3. Calling Convention: callconv(.C)
Different languages pass arguments to functions in different ways (on the Stack, in Registers, etc.).
- Zig uses its own "Fast" way by default.
- C uses the "C Standard" way.
- The Rule: If you use
export, Zig automatically uses the C calling convention. You don't have to worry about it!
3. Extern Structs: Matching the Memory
If you pass a struct between Zig and C, you must use extern struct.
- A normal Zig
structcan have its fields re-ordered by the compiler to save space. - An
extern structis Guaranteed to stay in the exact order you wrote it. This is mandatory so C knows where theidends and thenamebegins.
4. The C-ABI Reality: Register Pressure and Argument Passing
Zig's internal calling convention is aggressive and can change between versions. To "Play with C," you must freeze your logic into the Standard C-ABI.
The ABI Mirror
- The Process: For a function with 4 integers, the System V ABI (Linux) requires arguments to be placed in
%rdi,%rsi,%rdx, and%rcx. - The Physics: If you pass a large struct by value, the ABI may require it to be copied onto the stack. This Stack Traffic is the "Physics" of your performance bottleneck.
- Mastery: By using
extern structand passing by pointer, you ensure that Zig only moves a single 64-bit address through a register, bypassing the expensive stack-copying logic of the legacy C style.
5. Error Wrapping: c_int results
C does not understand Zig's "Error Union" (!u32). If a Zig function returns an error, C will just see a weird, random number.
The Fix: You must "Wrap" your logic.
5. Building the Library: .so and .dll
In build.zig, you must change your project type from an "Executable" to a "Shared Library."
Run zig build. You now have a file that a Python developer can import using ctypes or a C++ developer can link via a header.
FFI turns your Zig code into a "Universal Plugin." By mastering the export boundary and the discipline of extern-memory matching, you gain the ability to power libraries in Python, Ruby, Rust, and C++ with the extreme speed of Zig. You graduate from "Building an app" to "Architecting the Foundation."
Phase 22: FFI Architecture Checklist
- Audit your Exported Names: Ensure they don't conflict with standard C library names (e.g., don't export a function called
malloc). - Implement Sentinel Strings: When exporting strings, ensure you append a
:0null terminator so C can read them usingstrlen(). - Setup
extern structfor every data transfer object to ensure bit-identical layout between Zig and the host language. - Profile your Boundary Crossing: Use a performance profiler to measure the "FFI Overhead" in your host language (e.g., Python's
ctypescost). - Verify Symbol Visibility: Use the
nm -Dcommand (Linux) ordumpbin /exports(Windows) to verify that your Zig functions are correctly exposed in the final.soor.dll.
Read next: Zig as a C/C++ Compiler: The Zero-Legacy Orchestrator →
Part of the Zig Mastery Course — engineering the export.
