Skip to content

Appendix B. Zig Cheat Sheet

const std = @import"std";

B.1 Basic Program

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, Zig!\n", .{});
}

main is where the program starts. std is the standard library. std.debug.print prints formatted text.

B.2 Build and Run

zig run main.zig

Compile and run one file.

zig build-exe main.zig

Compile one file into an executable.

zig test main.zig

Run tests in one file.

zig build

Run the project build script.

B.3 Constants and Variables

const x = 10;
var y = 20;

Use const when the value does not change. Use var when the value changes.

var count: i32 = 0;
count += 1;

You can write the type explicitly after :.

B.4 Common Types

const a: i32 = -10;
const b: u32 = 10;
const c: f64 = 3.14;
const ok: bool = true;
const ch: u8 = 'A';

Common integer types:

TypeMeaning
i8, i16, i32, i64Signed integers
u8, u16, u32, u64Unsigned integers
usizeSize/index type
isizeSigned pointer-sized integer

B.5 Arrays and Slices

const nums = [_]i32{ 1, 2, 3 };

This is a fixed array.

const first = nums[0];

Indexing starts at 0.

const part = nums[0..2];

This creates a slice containing items at indexes 0 and 1.

Array type:

[3]i32

Slice type:

[]const i32

B.6 Strings

const name = "Zig";

A string literal is a slice of bytes.

const text: []const u8 = "hello";

Zig strings are usually UTF-8 bytes. Zig does not hide text encoding from you.

B.7 If

if (x > 0) {
    std.debug.print("positive\n", .{});
} else {
    std.debug.print("not positive\n", .{});
}

if can also produce a value:

const sign = if (x >= 0) 1 else -1;

B.8 Switch

const result = switch (value) {
    0 => "zero",
    1 => "one",
    else => "many",
};

switch must handle all possible cases, or it must include else.

B.9 While Loop

var i: usize = 0;

while (i < 5) : (i += 1) {
    std.debug.print("{}\n", .{i});
}

The expression after : runs after each loop iteration.

B.10 For Loop

const nums = [_]i32{ 10, 20, 30 };

for (nums) |n| {
    std.debug.print("{}\n", .{n});
}

With index:

for (nums, 0..) |n, i| {
    std.debug.print("{}: {}\n", .{ i, n });
}

B.11 Functions

fn add(a: i32, b: i32) i32 {
    return a + b;
}

Call it:

const x = add(2, 3);

A function that returns nothing uses void:

fn sayHello() void {
    std.debug.print("hello\n", .{});
}

B.12 Errors

const MyError = error{
    NotFound,
    InvalidInput,
};

A function that can fail:

fn load() MyError!void {
    return MyError.NotFound;
}

Use try to propagate the error:

try load();

Use catch to handle it:

load() catch |err| {
    std.debug.print("error: {}\n", .{err});
};

B.13 Optionals

var maybe_number: ?i32 = null;

An optional value is either a value or null.

maybe_number = 42;

Unwrap with if:

if (maybe_number) |n| {
    std.debug.print("{}\n", .{n});
}

B.14 Pointers

var x: i32 = 10;
const p: *i32 = &x;

&x means “address of x.”

p.* = 20;

p.* means “the value pointed to by p.”

B.15 Structs

const Point = struct {
    x: i32,
    y: i32,
};

Create a value:

const p = Point{
    .x = 10,
    .y = 20,
};

Access fields:

std.debug.print("{} {}\n", .{ p.x, p.y });

B.16 Methods

const Point = struct {
    x: i32,
    y: i32,

    fn lengthSquared(self: Point) i32 {
        return self.x * self.x + self.y * self.y;
    }
};

Call it:

const n = p.lengthSquared();

B.17 Enums

const Direction = enum {
    north,
    south,
    east,
    west,
};

Use with switch:

switch (dir) {
    .north => {},
    .south => {},
    .east => {},
    .west => {},
}

B.18 Unions

const Value = union(enum) {
    int: i32,
    float: f64,
    text: []const u8,
};

Use with switch:

switch (value) {
    .int => |n| std.debug.print("{}\n", .{n}),
    .float => |f| std.debug.print("{}\n", .{f}),
    .text => |s| std.debug.print("{s}\n", .{s}),
}

B.19 Defer

const file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close();

defer runs at the end of the current scope.

Use it for cleanup.

B.20 Allocators

const allocator = std.heap.page_allocator;

Allocate memory:

const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer);

Many Zig APIs ask for an allocator explicitly.

B.21 ArrayList

var list = std.ArrayList(i32).init(allocator);
defer list.deinit();

try list.append(10);
try list.append(20);

Read items:

for (list.items) |item| {
    std.debug.print("{}\n", .{item});
}

B.22 HashMap

var map = std.StringHashMap(i32).init(allocator);
defer map.deinit();

try map.put("one", 1);
try map.put("two", 2);

Get a value:

if (map.get("one")) |value| {
    std.debug.print("{}\n", .{value});
}

B.23 Comptime

fn identity(comptime T: type, value: T) T {
    return value;
}

Use it:

const a = identity(i32, 10);
const b = identity([]const u8, "hello");

comptime means the value is known while the program is being compiled.

B.24 Builtins

Zig builtins start with @.

@import("std")
@sizeOf(i32)
@alignOf(i32)
@typeInfo(i32)
@panic("failed")

Common examples:

BuiltinUse
@importImport a file or package
@sizeOfGet type size in bytes
@alignOfGet type alignment
@typeInfoInspect a type
@panicStop the program
@compileErrorEmit a compile-time error

B.25 Tests

test "add works" {
    try std.testing.expect(add(2, 3) == 5);
}

Run tests:

zig test main.zig

B.26 Common Format Strings

std.debug.print("{}\n", .{number});
std.debug.print("{s}\n", .{string});
std.debug.print("{any}\n", .{value});
FormatMeaning
{}Default formatting
{s}String
{any}Debug-style formatting

B.27 Imports

const std = @import("std");
const math = @import("math.zig");

If math.zig contains:

pub fn add(a: i32, b: i32) i32 {
    return a + b;
}

You can call:

const x = math.add(1, 2);

B.28 Public Declarations

pub fn run() void {}

pub makes a declaration visible outside the file or namespace.

Without pub, the declaration is private to that file or namespace.

B.29 Undefined

var x: i32 = undefined;

undefined means the value is not initialized.

Do not read an undefined value. Use it only when you will definitely assign a real value before reading.

B.30 Unreachable

unreachable;

Use unreachable to say: execution must never reach this point.

Example:

switch (value) {
    0 => {},
    1 => {},
    else => unreachable,
}

Use it carefully. If the program reaches unreachable, that is a bug.

B.31 Small Mental Model

Zig code usually asks you to make things visible:

QuestionZig usually wants
Can this fail?Put the error in the type
Does this allocate?Pass an allocator
Is this nullable?Use ?T
Is this compile-time?Use comptime
Is cleanup needed?Use defer
Is memory shared?Use pointers or slices explicitly

The cheat sheet is enough for reading small Zig programs. The details come from the chapters.