C

C

Although Zig is independent of C, and, unlike most other languages, does not depend on libc, Zig acknowledges the importance of interacting with existing C code.

There are a few ways that Zig facilitates C interop.

C Type Primitives

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

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

See also:

  • Primitive Types

C String Literals

test.zig

extern fn puts([*]const u8) void;

pub fn main() void {
    puts(c"this has a null terminator");
    puts(
        c\\and so
        c\\does this
        c\\multiline C string literal
    );
}
``` ```
$ zig build-exe test.zig --library c
$ ./test
this has a null terminator
and so
does this
multiline C string literal

See also:

  • String Literals and Character Literals

Import from C Header File

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

test.zig

const c = @cImport({
    // See https://github.com/ziglang/zig/issues/515
    @cDefine("_NO_CRT_STDIO_INLINE", "1");
    @cInclude("stdio.h");
});
pub fn main() void {
    _ = c.printf(c"hello\n");
}
``` ```
$ zig build-exe test.zig --library c
$ ./test
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:

const builtin = @import("builtin");

const c = @cImport({
    @cDefine("NDEBUG", builtin.mode == builtin.Mode.ReleaseFast);
    if (something) {
        @cDefine("_GNU_SOURCE", {});
    }
    @cInclude("stdlib.h");
    if (something) {
        @cUndef("_GNU_SOURCE");
    }
    @cInclude("soundio.h");
});

See also:

  • @cImport
  • @cInclude
  • @cDefine
  • @cUndef
  • @import

C Pointers

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.

[*c]T - C pointer.

  • Supports all the syntax of the other two pointer types.
  • Implicitly casts to other pointer types, as well as Optional Pointers. When a C pointer is implicitly casted to a non-optional pointer, safety-checked Undefined Behavior occurs if the address is 0.
  • 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 Optional Pointers.
  • Supports implicit casting to and from integers.
  • Supports comparison with integers.
  • Does not support Zig-only pointer attributes such as alignment. Use normal Pointers please!

Exporting a C Library

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

export fn add(a: i32, b: i32) i32 {
    return a + b;
}

To make a shared library:

$ zig build-lib mathtest.zig

To make a static library:

$ zig build-lib mathtest.zig --static

Here is an example with the Zig Build System:

test.c

// This header is generated by zig from mathtest.zig
#include "mathtest.h"
#include <assert.h>

int main(int argc, char **argv) {
    assert(add(42, 1337) == 1379);
    return 0;
}

build.zig

const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));

    const exe = b.addCExecutable("test");
    exe.addCompileFlags([][]const u8{"-std=c99"});
    exe.addSourceFile("test.c");
    exe.linkLibrary(lib);

    b.default_step.dependOn(&exe.step);

    const run_cmd = exe.run();

    const test_step = b.step("test", "Test the program");
    test_step.dependOn(&run_cmd.step);
}

terminal

$ zig build
$ ./test
$ echo $?
0

See also:

  • export

Mixing Object Files

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

base64.zig

const base64 = @import("std").base64;

export fn decode_base_64(
    dest_ptr: [*]u8,
    dest_len: usize,
    source_ptr: [*]const u8,
    source_len: usize,
) usize {
    const src = source_ptr[0..source_len];
    const dest = dest_ptr[0..dest_len];
    const base64_decoder = base64.standard_decoder_unsafe;
    const decoded_size = base64_decoder.calcSize(src);
    base64_decoder.decode(dest[0..decoded_size], src);
    return decoded_size;
}

test.c

// This header is generated by zig from base64.zig
#include "base64.h"

#include <string.h>
#include <stdio.h>

int main(int argc, char **argv) {
    const char *encoded = "YWxsIHlvdXIgYmFzZSBhcmUgYmVsb25nIHRvIHVz";
    char buf[200];

    size_t len = decode_base_64(buf, 200, encoded, strlen(encoded));
    buf[len] = 0;
    puts(buf);

    return 0;
}

build.zig

const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const obj = b.addObject("base64", "base64.zig");

    const exe = b.addCExecutable("test");
    exe.addCompileFlags([][]const u8 {
        "-std=c99",
    });
    exe.addSourceFile("test.c");
    exe.addObject(obj);
    exe.setOutputPath(".");

    b.default_step.dependOn(&exe.step);
}

terminal

$ zig build
$ ./test
all your base are belong to us

See also:

  • Targets
  • Zig Build System