C

    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:

    C String Literals

    test.zig

    1. $ zig build-exe test.zig --library c
    2. $ ./test
    3. this has a null terminator
    4. and so
    5. does this
    6. multiline C string literal

    See also:

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

    test.zig

    1. const c = @cImport({
    2. // See https://github.com/ziglang/zig/issues/515
    3. @cDefine("_NO_CRT_STDIO_INLINE", "1");
    4. @cInclude("stdio.h");
    5. });
    6. pub fn main() void {
    7. _ = c.printf(c"hello\n");
    8. }
    1. $ zig build-exe test.zig --library c
    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 == builtin.Mode.ReleaseFast);
    4. if (something) {
    5. @cDefine("_GNU_SOURCE", {});
    6. }
    7. @cInclude("stdlib.h");
    8. if (something) {
    9. @cUndef("_GNU_SOURCE");
    10. @cInclude("soundio.h");

    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 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 .
    • Supports implicit casting to and from integers.
    • Supports comparison with integers.
    • Does not support Zig-only pointer attributes such as alignment. Use normal please!

    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

    To make a shared library:

    1. $ zig build-lib mathtest.zig

    To make a static library:

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

    Here is an example with the Zig Build System:

    1. // This header is generated by zig from mathtest.zig
    2. #include "mathtest.h"
    3. #include <assert.h>
    4. int main(int argc, char **argv) {
    5. assert(add(42, 1337) == 1379);
    6. return 0;
    7. }

    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.addCExecutable("test");
    5. exe.addCompileFlags([][]const u8{"-std=c99"});
    6. exe.addSourceFile("test.c");
    7. exe.linkLibrary(lib);
    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:

    Mixing Object Files

    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_ptr: [*]u8,
    4. source_ptr: [*]const u8,
    5. source_len: usize,
    6. ) usize {
    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.addCExecutable("test");
    5. exe.addCompileFlags([][]const u8 {
    6. "-std=c99",
    7. });
    8. exe.addSourceFile("test.c");
    9. exe.addObject(obj);
    10. exe.setOutputPath(".");
    11. b.default_step.dependOn(&exe.step);
    12. }

    terminal

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