Appendix B. Zig Cheat Sheet

const std = @import"std";

Appendix B. Zig Cheat Sheet

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:

Type Meaning
i8, i16, i32, i64 Signed integers
u8, u16, u32, u64 Unsigned integers
usize Size/index type
isize Signed 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:

Builtin Use
@import Import a file or package
@sizeOf Get type size in bytes
@alignOf Get type alignment
@typeInfo Inspect a type
@panic Stop the program
@compileError Emit 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});
Format Meaning
{} 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:

Question Zig 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.