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

  1. extern fn puts([*]const u8) void;
  2. pub fn main() void {
  3. puts(c"this has a null terminator");
  4. puts(
  5. c\\and so
  6. c\\does this
  7. c\\multiline C string literal
  8. );
  9. }
  1. $ zig build-exe test.zig -lc
  2. $ ./test
  3. this has a null terminator
  4. and so
  5. does this
  6. 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

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

  1. const builtin = @import("builtin");
  2. const c = @cImport({
  3. @cDefine("NDEBUG", builtin.mode == 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 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!

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

  1. export fn add(a: i32, b: i32) i32 {
  2. return a + b;
  3. }

To make a static library:

  1. $ zig build-lib mathtest.zig

To make a shared library:

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

terminal

  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_unsafe;
  11. const decoded_size = base64_decoder.calcSize(src);
  12. base64_decoder.decode(dest[0..decoded_size], src);
  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. }

terminal

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

See also: