Zig notes

Print formatting:

@<identifier> is a built-in.

@compileLog(@TypeOf(val)) will output the type name of val.

! is result (error_type!success_type) where !success_type alone infers the error. If a function doesn't (possibly) return an error then the ! is redundant.

To run project: zig make run, to build: zig make build

_ will dispose of the value (like let _ binding in Rust)_ will dispose of the value (like let _ binding in Rust)

switch can only match on values known at compile time |n| is called a capture

Example also contains enum.

const MyEnum = enum {

pub fn main() !void {
    const val = MyEnum.A;

    switch (val) {
        MyEnum.A => {},
        MyEnum.B => {},
        else => |probably_c| {


comptime_int: example const value: comptime_int = 5; The main use case of comptime_int is type inference.


Defining your own error type is similar to an enum.

const MyError = error {

Using errors:

fn throw() MyError!void {
    return error.ErrorVariantB;

Dealing with errors:

function_that_generates_errors() catch |err| switch (err) {
    error.ErrorVariantA => {},
    error.ErrorVariantB => {}


Can be represented as a numerical value using @enumToInt as long the as enum is defined as enum(some_int_type).

Assigning a numerical value to a variant means the next variant will have the numerical value + 1. It is not necessary to assign a value higher than the previous value of the previous variant.

const EnumA = enum {

// We can also represent enums as u8

const EnumB = enum(u8) {
    A, // <- 0
    B // <- 1

const EnumC = enum(u8) {
    A = 100,// <- 100
    B,      // <- 101
    C,      // <- 102
    D = 1,  // <- 1
    E,      // <- 2

var value: EnumB = .B;
var byte = @enumToInt(value); // u8 is inferred


Prefix a type with ? makes it into an optional.

var number: ?usize = null;

Using orelse we can do an early return or assign a defaut value;

Default value:

var nullable: ?u8 = null;
var not_null = nullable orelse 0;

Early return:

var nullable: ?u8 = null;
var not_null = nullable orelse return;


String literals = const [N;0]u8. String literals are pointers to arrays. String literals live in ro memory.

const string = "hello"; // *const [5:0]u8;

String literals can coerce into []const u8 (from *const [L:0]u8). Note: [Len;0] where zero means null terminated.

String literal to byte array is done via coercion:

const string = "hello";
const byte_arr: []const u8 = string;

Arrays and Slices

var arr = [_]u8{1, 2, 3};
var slice: []const u8 = &arr;

var alt_array: [2]u8 = .{1, 2};


Notes on struct declaration: var / const should be terminated with semicolon, whereas fields should have a comma.

const AStruct = struct {
    const Self = @Type(); // semicolon because it's not a field
    val: u32,

var inst = AStruct { val = 213 };

Anonymous structs:

const variable_name = .{ field_name: u32 = 123 };

const nameless = .{4,8};
var value = nameless.@"0"; // = 4
var other_value = nameless[1]; // = 8

Packed structs

TODO: needs more info

const PackyStruct = packed struct {
    a: u2,
    b: u2,
    c: u2,
    d: u2,

Unions and Tagged unions

A union has to be tagged (union(enum) as opposed to just union) to be able to use with a switch statement.

// Only one value
const SomeUnion = union {
    A: u16,
    B: [2]u8,

// Can switch on an instance of this
const Taggy = union(enum) {
    A: u16
    B: [2]u8


Creating a module is a case of adding a file and importing said file:

// mymod.zig
pub const VALUE: u32 = 1;
// main.zig
const module_name = @import("mymod.zig");
const other_module = @import("dir/mymod.zig");

pub fn main() void {
    const value = module_name.VALUE;

External modules

Using an external Rust libary:

Modify the build.zig and call the following functions on the exe object:

// main.zig
extern fn greet() void;

pub fn main() anyerror!void {
// build.zig
const exe = b.addExecutable("ffibits", "src/main.zig");
//... Everything  else
exe.linkSystemLibrary("c"); // Includes libc
exe.linkSystemLibrary("myname"); // Name of the library
exe.addLibPath("./myname/target/release/"); // Add the path to where the library is
// ...

This code makes the assumption that there will be a file in "./myname/target/release/" named "libmyname.so" or "libmyname.a" (on linux)

NOTE: At the time of writing there is presumably a bug: When rebuilding the library it isn't updated in the Zig code until you touch some of the Zig code using it.

Optionally one can omit exe.linkSystemLibrary("myname"); if you change:

extern fn greet() void;


extern "myname" fn greet() void;