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:

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

    test.zig

    Shell

    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:

    @cImport Expression

    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:

    C Translation CLI

    Zig’s C translation capability is available as a CLI tool via zig translate-c. It requires a single filename as an argument. It may also take a set of optional flags that are forwarded to clang. It writes the translated file to stdout.

    • -I: Specify a search directory for include files. May be used multiple times. Equivalent to . The current directory is not included by default; use -I. to include it.
    • -D: Define a preprocessor macro. Equivalent to clang’s -D flag.
    • -cflags [flags] —: Pass arbitrary additional to clang. Note: the list of flags must end with --
    • -target: The target triple for the translated Zig code. If no target is specified, the current host target will be used.

    Important! When translating C code with zig translate-c, you must use the same -target triple that you will use when compiling the translated code. In addition, you must ensure that the -cflags used, if any, match the cflags used by code on the target system. Using the incorrect -target or -cflags could result in clang or Zig parse failures, or subtle ABI incompatibilities when linking with C code.

    varytarget.h

    1. long FOO = __LONG_MAX__;

    Shell

    1. $ zig translate-c -target thumb-freestanding-gnueabihf varytarget.h|grep FOO
    2. pub export var FOO: c_long = 2147483647;
    3. $ zig translate-c -target x86_64-macos-gnu varytarget.h|grep FOO
    4. pub export var FOO: c_long = 9223372036854775807;

    varycflags.h

    1. enum FOO { BAR };
    2. int do_something(enum FOO foo);

    Shell

    1. $ zig translate-c varycflags.h|grep -B1 do_something
    2. pub const enum_FOO = c_uint;
    3. pub extern fn do_something(foo: enum_FOO) c_int;
    4. pub const enum_FOO = u8;
    5. pub extern fn do_something(foo: enum_FOO) c_int;

    @cImport and zig translate-c use the same underlying C translation functionality, so on a technical level they are equivalent. In practice, @cImport is useful as a way to quickly and easily access numeric constants, typedefs, and record types without needing any extra setup. If you need to pass to clang, or if you would like to edit the translated code, it is recommended to use zig translate-c and save the results to a file. Common reasons for editing the generated code include: changing anytype parameters in function-like macros to more specific types; changing [*c]T pointers to [*]T or *T pointers for improved type safety; and enabling or disabling runtime safety within specific functions.

    See also:

    The C translation feature (whether used via zig translate-c or @cImport) integrates with the Zig caching system. Subsequent runs with the same source file, target, and cflags will use the cache instead of repeatedly translating the same code.

    To see where the cached files are stored when compiling code that uses @cImport, use the --verbose-cimport flag:

    Shell

    1. $ zig build-exe verbose.zig -lc --verbose-cimport
    2. info(compilation): C import source: docgen_tmp/zig-cache/o/086ec5181a4d9a8461213ac56b50f503/cimport.h
    3. info(compilation): C import .d file: docgen_tmp/zig-cache/o/086ec5181a4d9a8461213ac56b50f503/cimport.h.d
    4. info(compilation): C import output: docgen_tmp/zig-cache/o/102137712066d77608ec368092fa98d6/cimport.zig
    5. $ ./verbose

    cimport.h contains the file to translate (constructed from calls to @cInclude, @cDefine, and @cUndef), cimport.h.d is the list of file dependencies, and cimport.zig contains the translated output.

    See also:

    Translation failures

    Some C constructs cannot be translated to Zig - for example, goto, structs with bitfields, and token-pasting macros. Zig employs demotion to allow translation to continue in the face of non-translatable entities.

    Demotion comes in three varieties - , extern, and @compileError. C structs and unions that cannot be translated correctly will be translated as opaque{}. Functions that contain opaque types or code constructs that cannot be translated will be demoted to extern declarations. Thus, non-translatable types can still be used as pointers, and non-translatable functions can be called so long as the linker is aware of the compiled function.

    @compileError is used when top-level definitions (global variables, function prototypes, macros) cannot be translated or demoted. Since Zig uses lazy analysis for top-level declarations, untranslatable entities will not cause a compile error in your code unless you actually use them.

    See also:

    C Macros

    C Translation makes a best-effort attempt to translate function-like macros into equivalent Zig functions. Since C macros operate at the level of lexical tokens, not all C macros can be translated to Zig. Macros that cannot be translated will be be demoted to @compileError. Note that C code which uses macros will be translated without any additional issues (since Zig operates on the pre-processed source with macros expanded). It is merely the macros themselves which may not be translatable to Zig.

    Consider the following example:

    macro.c

    1. #define MAKELOCAL(NAME, INIT) int NAME = INIT
    2. int foo(void) {
    3. MAKELOCAL(a, 1);
    4. MAKELOCAL(b, 2);
    5. return a + b;
    6. }

    Shell

    1. $ zig translate-c macro.c > macro.zig

    macro.zig

    1. pub export fn foo() c_int {
    2. var a: c_int = 1;
    3. var b: c_int = 2;
    4. return a + b;
    5. }
    6. pub const MAKELOCAL = @compileError("unable to translate C expr: unexpected token .Equal"); // macro.c:1:9

    Note that foo was translated correctly despite using a non-translatable macro. MAKELOCAL was demoted to @compileError since it cannot be expressed as a Zig function; this simply means that you cannot directly use MAKELOCAL from Zig.

    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 many-item 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.
    • Coerces to other pointer types, as well as Optional Pointers. When a C pointer is coerced 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 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

    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. export fn add(a: i32, b: i32) i32 {
    2. return a + b;
    3. }

    To make a static library:

    Shell

    1. $ zig build-lib mathtest.zig

    To make a shared library:

    Shell

    Here is an example with the Zig Build System:

    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. exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
    4. exe.linkLibrary(lib);
    5. exe.linkSystemLibrary("c");
    6. b.default_step.dependOn(&exe.step);
    7. const run_cmd = exe.run();
    8. const test_step = b.step("test", "Test the program");
    9. test_step.dependOn(&run_cmd.step);
    10. }

    Shell

    1. $ zig build test
    2. 1379

    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_ptr: [*]u8,
    4. dest_len: usize,
    5. source_ptr: [*]const u8,
    6. source_len: usize,
    7. ) usize {
    8. const src = source_ptr[0..source_len];
    9. const dest = dest_ptr[0..dest_len];
    10. const base64_decoder = base64.standard.Decoder;
    11. const decoded_size = base64_decoder.calcSizeForSlice(src) catch unreachable;
    12. base64_decoder.decode(dest[0..decoded_size], src) catch unreachable;
    13. return decoded_size;
    14. }

    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. }

    Shell

    See also: