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
$ zig build-exe test.zig -lc
$ ./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:
@cImport Expression
const builtin = @import("builtin");
const c = @cImport({
@cDefine("NDEBUG", builtin.mode == .ReleaseFast);
if (something) {
@cDefine("_GNU_SOURCE", {});
}
@cInclude("stdlib.h");
if (something) {
@cUndef("_GNU_SOURCE");
}
@cInclude("soundio.h");
});
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
long FOO = __LONG_MAX__;
Shell
$ zig translate-c -target thumb-freestanding-gnueabihf varytarget.h|grep FOO
pub export var FOO: c_long = 2147483647;
$ zig translate-c -target x86_64-macos-gnu varytarget.h|grep FOO
pub export var FOO: c_long = 9223372036854775807;
varycflags.h
enum FOO { BAR };
int do_something(enum FOO foo);
Shell
$ zig translate-c varycflags.h|grep -B1 do_something
pub const enum_FOO = c_uint;
pub extern fn do_something(foo: enum_FOO) c_int;
pub const enum_FOO = u8;
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
$ zig build-exe verbose.zig -lc --verbose-cimport
info(compilation): C import source: docgen_tmp/zig-cache/o/086ec5181a4d9a8461213ac56b50f503/cimport.h
info(compilation): C import .d file: docgen_tmp/zig-cache/o/086ec5181a4d9a8461213ac56b50f503/cimport.h.d
info(compilation): C import output: docgen_tmp/zig-cache/o/102137712066d77608ec368092fa98d6/cimport.zig
$ ./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
#define MAKELOCAL(NAME, INIT) int NAME = INIT
int foo(void) {
MAKELOCAL(a, 1);
MAKELOCAL(b, 2);
return a + b;
}
Shell
$ zig translate-c macro.c > macro.zig
macro.zig
pub export fn foo() c_int {
var a: c_int = 1;
var b: c_int = 2;
return a + b;
}
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
export fn add(a: i32, b: i32) i32 {
return a + b;
}
To make a static library:
Shell
$ zig build-lib mathtest.zig
To make a shared library:
Shell
Here is an example with the Zig Build System:
test.c
// This header is generated by zig from mathtest.zig
#include "mathtest.h"
#include <stdio.h>
int main(int argc, char **argv) {
int32_t result = add(42, 1337);
printf("%d\n", result);
return 0;
}
build.zig
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
exe.linkLibrary(lib);
exe.linkSystemLibrary("c");
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);
}
Shell
$ zig build test
1379
See also:
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;
const decoded_size = base64_decoder.calcSizeForSlice(src) catch unreachable;
base64_decoder.decode(dest[0..decoded_size], src) catch unreachable;
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.addExecutable("test", null);
exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
exe.addObject(obj);
exe.linkSystemLibrary("c");
exe.install();
}
Shell
See also: