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
c_void
See also:
C String Literals
test.zig
extern fn puts([*]const u8) void;
pub fn main() void {
puts(c"this has a null terminator");
puts(
c\\and so
c\\does this
c\\multiline C string literal
);
}
$ zig build-exe test.zig --library c
$ ./test
this has a null terminator
and so
does this
multiline C string literal
See also:
Import from C Header File
The @cImport
builtin function can be used to directly import symbols from .h files:
test.zig
const c = @cImport({
// See https://github.com/ziglang/zig/issues/515
@cDefine("_NO_CRT_STDIO_INLINE", "1");
@cInclude("stdio.h");
});
pub fn main() void {
_ = c.printf(c"hello\n");
}
$ zig build-exe test.zig --library c
$ ./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:
const builtin = @import("builtin");
const c = @cImport({
@cDefine("NDEBUG", builtin.mode == builtin.Mode.ReleaseFast);
if (something) {
@cDefine("_GNU_SOURCE", {});
}
@cInclude("stdlib.h");
if (something) {
@cUndef("_GNU_SOURCE");
}
@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.
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 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 implicit casting to and from integers.
- Supports comparison with integers.
- Does not support Zig-only pointer attributes such as alignment. Use normal Pointers please!
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
export fn add(a: i32, b: i32) i32 {
return a + b;
}
To make a shared library:
$ zig build-lib mathtest.zig
To make a static library:
$ zig build-lib mathtest.zig --static
Here is an example with the Zig Build System:
test.c
// This header is generated by zig from mathtest.zig
#include "mathtest.h"
#include <assert.h>
int main(int argc, char **argv) {
assert(add(42, 1337) == 1379);
return 0;
}
build.zig
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));
const exe = b.addCExecutable("test");
exe.addCompileFlags([][]const u8{"-std=c99"});
exe.addSourceFile("test.c");
exe.linkLibrary(lib);
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);
}
terminal
$ zig build
$ ./test
$ echo $?
0
See also:
Mixing Object Files
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_unsafe;
const decoded_size = base64_decoder.calcSize(src);
base64_decoder.decode(dest[0..decoded_size], src);
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.addCExecutable("test");
exe.addCompileFlags([][]const u8 {
"-std=c99",
});
exe.addSourceFile("test.c");
exe.addObject(obj);
exe.setOutputPath(".");
b.default_step.dependOn(&exe.step);
}
terminal
$ zig build
$ ./test
all your base are belong to us
See also: