Zig

Essential Zig syntax, memory management, and systems programming patterns for building robust software.

languages
zigsystemslow-levelmemory-safe

Hello World

const std = @import("std");

pub fn main() !void {
    try std.fs.File.stdout().writeAll("Hello world!\n");
}

Variables

// Immutable variable (preferred)
const x: i32 = 42;

// Mutable variable
var y: i32 = 10;
y = 20;

// Type inference
const z = 100; // inferred as comptime_int

// Undefined (uninitialized)
var buffer: [100]u8 = undefined;

Data Types

// Integers
const a: i8 = -128;      // signed 8-bit
const b: u32 = 1000;     // unsigned 32-bit
const c: usize = 42;     // pointer-sized unsigned

// Floats
const f: f32 = 3.14;
const d: f64 = 2.71828;

// Boolean
const flag: bool = true;

// Optional
const maybe: ?i32 = null;
const value: ?i32 = 42;

// Error union
const result: anyerror!i32 = 42;

Arrays and Slices

// Fixed-size array
const arr = [_]i32{ 1, 2, 3, 4, 5 };

// Access elements
const first = arr[0];

// Array length
const len = arr.len;

// Slice (pointer + length)
const slice: []const i32 = arr[1..4];

// Iterate over array
for (arr) |item| {
    // use item
}

// With index
for (arr, 0..) |item, index| {
    // use item and index
}

Strings

// String literals are []const u8
const hello = "Hello, World!";

// Multiline strings
const multiline =
    \\Line 1
    \\Line 2
    \\Line 3
;

// String concatenation (comptime)
const greeting = "Hello, " ++ "Zig!";

// Format strings
const std = @import("std");
var buf: [100]u8 = undefined;
const formatted = std.fmt.bufPrint(&buf, "Value: {d}", .{42});

Functions

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

// Function with error return
fn divide(a: f64, b: f64) !f64 {
    if (b == 0) return error.DivisionByZero;
    return a / b;
}

// Generic function
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

// Inline function
inline fn square(x: i32) i32 {
    return x * x;
}

Control Flow

// If expression
const result = if (x > 0) "positive" else "non-positive";

// If statement
if (condition) {
    // do something
} else if (other_condition) {
    // do something else
} else {
    // default
}

// Switch expression
const output = switch (value) {
    1 => "one",
    2, 3 => "two or three",
    4...10 => "four to ten",
    else => "other",
};

// While loop
while (condition) {
    // loop body
}

// While with continue expression
var i: usize = 0;
while (i < 10) : (i += 1) {
    // loop body
}

Structs

// Define a struct
const Point = struct {
    x: f64,
    y: f64,

    // Method
    pub fn distance(self: Point, other: Point) f64 {
        const dx = self.x - other.x;
        const dy = self.y - other.y;
        return @sqrt(dx * dx + dy * dy);
    }

    // Associated function
    pub fn origin() Point {
        return Point{ .x = 0, .y = 0 };
    }
};

// Create instance
const p = Point{ .x = 3.0, .y = 4.0 };

// Access fields
const x_val = p.x;

// Call method
const dist = p.distance(Point.origin());

Enums

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

// Enum with values
const HttpStatus = enum(u16) {
    ok = 200,
    not_found = 404,
    internal_error = 500,
};

// Tagged union
const Value = union(enum) {
    int: i32,
    float: f64,
    string: []const u8,
    none,
};

const v = Value{ .int = 42 };
switch (v) {
    .int => |i| // use i,
    .float => |f| // use f,
    else => {},
}

Error Handling

// Define errors
const FileError = error{
    NotFound,
    PermissionDenied,
    EndOfFile,
};

// Return error
fn readFile(path: []const u8) FileError![]u8 {
    if (path.len == 0) return error.NotFound;
    // ...
}

// Try operator (propagate error)
const data = try readFile("test.txt");

// Catch with default
const result = readFile("test.txt") catch "default";

// Catch with handler
const value = readFile("test.txt") catch |err| {
    std.debug.print("Error: {}\n", .{err});
    return err;
};

// If error capture
if (readFile("test.txt")) |data| {
    // success
} else |err| {
    // handle error
}

Optionals

// Optional type
var maybe_value: ?i32 = null;
maybe_value = 42;

// Unwrap with orelse
const value = maybe_value orelse 0;

// Unwrap with if
if (maybe_value) |v| {
    // use v
}

// Optional pointer
const ptr: ?*i32 = null;
if (ptr) |p| {
    p.* = 10;
}

Memory Management

Memory Allocators

AllocatorUse CaseSafetyPerformanceNotes
GeneralPurposeAllocatorProduction codeHighGoodDetects memory leaks and double-frees
ArenaAllocatorTemporary/batch allocationsHighExcellentFree all at once, no individual frees
FixedBufferAllocatorStack/embedded systemsMediumExcellentFixed-size buffer, no dynamic allocation
page_allocatorLarge allocationsMediumGoodDirect OS memory pages (slow for small allocs)
c_allocatorC interopLowGoodWraps malloc/free

Basic Allocation

const std = @import("std");

// GeneralPurposeAllocator (recommended for most use cases)
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // Check for leaks
const allocator = gpa.allocator();

// Allocate single item
const ptr = try allocator.create(i32);
defer allocator.destroy(ptr);
ptr.* = 42;

// Allocate slice
const slice = try allocator.alloc(u8, 100);
defer allocator.free(slice);

Arena Allocator

// Arena - allocate many items, free all at once
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // Frees everything

const arena_allocator = arena.allocator();

// No need for individual frees
const items = try arena_allocator.alloc(i32, 100);
const more = try arena_allocator.create(MyStruct);
// All freed with arena.deinit()

Fixed Buffer Allocator

// Fixed buffer (stack allocation)
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const fba_allocator = fba.allocator();

// Limited to buffer size, no heap allocation
const data = try fba_allocator.alloc(u8, 100);

Working with Collections

// ArrayList
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
try list.append(1);
try list.append(2);

// HashMap
var map = std.AutoHashMap([]const u8, i32).init(allocator);
defer map.deinit();
try map.put("key", 42);

File I/O

Reading Files

const std = @import("std");

// Read entire file into memory
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();

const allocator = gpa.allocator();

const data = try std.fs.cwd().readFileAlloc(allocator, "./some_file.txt", 1024 * 1024);
defer allocator.free(data);

std.debug.print("File Contents: {s}\n", .{data});

Writing Files

const std = @import("std");

const file = try std.fs.cwd().createFile("./some-file.txt", .{});
defer file.close();

try file.writeAll("Hello, Zig File I/O!\n");

User Input

const std = @import("std");

pub fn main() !void {
    try std.fs.File.stdout().writeAll("Enter your name: ");

    // Get input from stdin
    var stdin_buf: [256]u8 = undefined;
    var stdin_reader = std.fs.File.stdin().reader(&stdin_buf);
    const stdin = &stdin_reader.interface;
    const name = try stdin.takeDelimiter('\n');

    // Display output to stdout
    var stdout_buf: [256]u8 = undefined;
    var stdout_writer = std.fs.File.stdout().writer(&stdout_buf);
    const stdout = &stdout_writer.interface;

    try stdout.print("Hello, {s}", .{name orelse ""});
    try stdout.flush();
}

Comptime

// Compile-time evaluation
const computed = comptime blk: {
    var result: i32 = 0;
    for (0..10) |i| {
        result += @intCast(i);
    }
    break :blk result;
};

// Comptime parameter
fn repeat(comptime n: usize, value: u8) [n]u8 {
    return [_]u8{value} ** n;
}

// Type reflection
fn debugPrint(comptime T: type, value: T) void {
    const info = @typeInfo(T);
    // use type info
}

Pointers

// Single-item pointer
var x: i32 = 42;
const ptr: *i32 = &x;
ptr.* = 100;  // dereference

// Const pointer
const const_ptr: *const i32 = &x;

// Many-item pointer
const arr = [_]i32{ 1, 2, 3 };
const many_ptr: [*]const i32 = &arr;

// Pointer arithmetic
const second = many_ptr + 1;

// Sentinel-terminated pointer
const str: [*:0]const u8 = "hello";

Testing

const std = @import("std");
const expect = std.testing.expect;

test "basic test" {
    const x = 1 + 1;
    try expect(x == 2);
}

test "string equality" {
    const a = "hello";
    const b = "hello";
    try expect(std.mem.eql(u8, a, b));
}

// Run tests: zig test filename.zig

Imports and Modules

// Standard library import
const std = @import("std");

// Local file import
const utils = @import("utils.zig");

// Access public declarations
const result = utils.helperFunction();

// Built-in functions
const size = @sizeOf(i32);      // size in bytes
const aligned = @alignOf(i64);   // alignment
const type_name = @typeName(i32); // "i32"