There are a few ways that Zig facilitates C interop.

    These have guaranteed C ABI compatibility and can be used like any other type.

    • c_ushort
    • c_int
    • c_uint
    • c_long
    • c_ulong
    • c_longlong
    • c_ulonglong
    • c_longdouble
    • c_void

    See also:

    The @cImport builtin function can be used to directly import symbols from .h files:

    test.zig

    1. $ zig build-exe test.zig -lc
    2. $ ./test
    3. hello

    The @cImport function takes an expression as a parameter. This expression is evaluated at compile-time and is used to control preprocessor directives and include multiple .h files:

    1. const builtin = @import("builtin");
    2. const c = @cImport({
    3. @cDefine("NDEBUG", builtin.mode == .ReleaseFast);
    4. if (something) {
    5. @cDefine("_GNU_SOURCE", {});
    6. }
    7. @cInclude("stdlib.h");
    8. if (something) {
    9. @cUndef("_GNU_SOURCE");
    10. }
    11. @cInclude("soundio.h");
    12. });

    See also:

    This type is to be avoided whenever possible. The only valid reason for using a C pointer is in auto-generated code from translating C code.

    When importing C header files, it is ambiguous whether pointers should be translated as single-item pointers (*T) or unknown-length pointers ([*]T). C pointers are a compromise so that Zig code can utilize translated header files directly.

    • Supports all the syntax of the other two pointer types.
    • Allows address 0. On non-freestanding targets, dereferencing address 0 is safety-checked Undefined Behavior. Optional C pointers introduce another bit to keep track of null, just like ?usize. Note that creating an optional C pointer is unnecessary as one can use normal .
    • Supports Type Coercion to and from integers.
    • Supports comparison with integers.
    • Does not support Zig-only pointer attributes such as alignment. Use normal please!

    When a C pointer is pointing to a single struct (not an array), dereference the C pointer to access to the struct’s fields or member data. That syntax looks like this:

    ptr_to_struct.*.struct_member

    This is comparable to doing -> in C.

    When a C pointer is pointing to an array of structs, the syntax reverts to this:

    ptr_to_struct_array[index].struct_member

    One of the primary use cases for Zig is exporting a library with the C ABI for other programming languages to call into. The export keyword in front of functions, variables, and types causes them to be part of the library API:

    mathtest.zig

    1. return a + b;
    2. }

    To make a static library:

    To make a shared library:

    1. $ zig build-lib mathtest.zig -dynamic

    test.c

    1. // This header is generated by zig from mathtest.zig
    2. #include "mathtest.h"
    3. #include <stdio.h>
    4. int main(int argc, char **argv) {
    5. int32_t result = add(42, 1337);
    6. printf("%d\n", result);
    7. return 0;
    8. }

    build.zig

    1. const Builder = @import("std").build.Builder;
    2. pub fn build(b: *Builder) void {
    3. const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));
    4. const exe = b.addExecutable("test", null);
    5. exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
    6. exe.linkLibrary(lib);
    7. exe.linkSystemLibrary("c");
    8. b.default_step.dependOn(&exe.step);
    9. const run_cmd = exe.run();
    10. const test_step = b.step("test", "Test the program");
    11. test_step.dependOn(&run_cmd.step);
    12. }

    terminal

    See also:

    You can mix Zig object files with any other object files that respect the C ABI. Example:

    base64.zig

    1. const base64 = @import("std").base64;
    2. export fn decode_base_64(
    3. dest_len: usize,
    4. source_ptr: [*]const u8,
    5. ) usize {
    6. const src = source_ptr[0..source_len];
    7. const dest = dest_ptr[0..dest_len];
    8. const base64_decoder = base64.standard_decoder_unsafe;
    9. const decoded_size = base64_decoder.calcSize(src);
    10. base64_decoder.decode(dest[0..decoded_size], src);
    11. return decoded_size;
    12. }

    test.c

    1. // This header is generated by zig from base64.zig
    2. #include "base64.h"
    3. #include <string.h>
    4. #include <stdio.h>
    5. int main(int argc, char **argv) {
    6. const char *encoded = "YWxsIHlvdXIgYmFzZSBhcmUgYmVsb25nIHRvIHVz";
    7. char buf[200];
    8. size_t len = decode_base_64(buf, 200, encoded, strlen(encoded));
    9. buf[len] = 0;
    10. puts(buf);
    11. return 0;
    12. }

    build.zig

    1. const Builder = @import("std").build.Builder;
    2. pub fn build(b: *Builder) void {
    3. const obj = b.addObject("base64", "base64.zig");
    4. const exe = b.addExecutable("test", null);
    5. exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
    6. exe.addObject(obj);
    7. exe.linkSystemLibrary("c");
    8. exe.install();
    9. }

    terminal