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 -lc
    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 -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 == builtin.Mode.ReleaseFast);
    4. if (something) {
    5. @cDefine("_GNU_SOURCE", {});
    6. }
    7. @cInclude("stdlib.h");
    8. if (something) {
    9. }
    10. @cInclude("soundio.h");

    See also:

    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.

    [*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!

    When a C pointer is pointing to a single struct (not an array), deference 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

    To make a static library:

    1. $ zig build-lib mathtest.zig

    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:

    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 src = source_ptr[0..source_len];
    8. const dest = dest_ptr[0..dest_len];
    9. const base64_decoder = base64.standard_decoder_unsafe;
    10. const decoded_size = base64_decoder.calcSize(src);
    11. base64_decoder.decode(dest[0..decoded_size], src);
    12. return decoded_size;
    13. }

    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

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

    See also: