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

To interop with the C void type, use anyopaque.

See also:

Import from C Header File

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("hello\n");
  8. }

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.

Command line flags

  • -I: Specify a search directory for include files. May be used multiple times. Equivalent to clang’s -I flag. 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 command line flags 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.

Using -target and -cflags

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 <em>thumb-freestanding-gnueabihf</em> varytarget.h|grep FOO
  2. pub export var FOO: c_long = <em>2147483647</em>;
  3. $ zig translate-c -target <em>x86_64-macos-gnu</em> varytarget.h|grep FOO
  4. pub export var FOO: c_long = <em>9223372036854775807</em>;

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 = <em>c_uint</em>;
  3. pub extern fn do_something(foo: enum_FOO) c_int;
  4. $ zig translate-c <em>-cflags -fshort-enums --</em> varycflags.h|grep -B1 do_something
  5. pub const enum_FOO = <em>u8</em>;
  6. pub extern fn do_something(foo: enum_FOO) c_int;

@cImport vs translate-c

@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 cflags 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:

C Translation Caching

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:

verbose.zig

  1. const c = @cImport({
  2. @cDefine("_NO_CRT_STDIO_INLINE", "1");
  3. @cInclude("stdio.h");
  4. });
  5. pub fn main() void {
  6. _ = c;
  7. }

Shell

  1. $ zig build-exe verbose.zig -lc --verbose-cimport
  2. info(compilation): C import source: /home/ci/release-0.10.1/out/zig-local-cache/o/0547743b81bdeff51a49e52a5e93102c/cimport.h
  3. info(compilation): C import .d file: /home/ci/release-0.10.1/out/zig-local-cache/o/0547743b81bdeff51a49e52a5e93102c/cimport.h.d
  4. info(compilation): C import output: /home/ci/release-0.10.1/out/zig-local-cache/o/70dbdfea0db77295772596037204ea19/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 - opaque, 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 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:

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

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

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

  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

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

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

Shell

  1. $ zig build test
  2. 1379

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

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

See also: