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:
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("hello\n");
}
$ 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:
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 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 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
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 static library:
$ zig build-lib mathtest.zig
To make a shared library:
$ zig build-lib mathtest.zig -dynamic
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 {
const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));
const exe = b.addExecutable("test", null);
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);
}
terminal
$ zig build test
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
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();
}
terminal
$ zig build
$ ./zig-out/bin/test
all your base are belong to us
See also: