Rob Bergin

Zig Studylog

May 21, 2026

Session 1: Pointers and The Heap

Concepts

Zig specifics

Program #1: I just came to say hello

const std = @import("std");

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

Program #2: Exploring pointers and dereferencing

const std = @import("std");

pub fn main() void {
    var age: i32 = 8;
    const ptr = &age;  // & means "give me the address of age"

    std.debug.print("age is: {}\n", .{age});
    std.debug.print("address of age: {}\n", .{ptr}); // get address
    std.debug.print("value at address: {}\n", .{ptr.*}); // dereferencing
}

Program #3: Allocating memory on the heap

const std = @import("std");

pub fn main() !void {
	var gpa: std.heap.DebugAllocator(.{}) = .init;
	defer {
		const status = gpa.deinit();
		if (status == .leak) @panic("memory leak!");
	}
	
	const allocator = gpa.allocator();
	
	const numbers = try allocator.alloc(i32, 5);
	defer allocator.free(numbers);
	
	numbers[0] = 10;
	numbers[1] = 20;
	numbers[2] = 30;
	numbers[3] = 40;
	numbers[4] = 50;
	
	for (numbers) |n| {
		std.debug.print("{d}\n", .{n});
	}
}
May 23, 2026

Session 2: Slices

Concepts

Program #4: Arrays and slices

const std = @import("std");

pub fn main() void {
    var numbers = [5]i32{10, 20, 30, 40, 50};
    const slice = numbers[1..4];

    std.debug.print("full array length: {}\n", .{numbers.len});
    std.debug.print("slice length: {}\n", .{slice.len});

    for (slice) |n| {
        std.debug.print("{d}\n", .{n});
    }
}
May 25, 2026

Session 3: Functions and Error Handling

Concepts

Program #5: Functions with slice parameters

const std = @import("std");

fn sum(numbers: []const i32) i32 {
    var total: i32 = 0;
    for (numbers) |n| {
        total += n;
    }
    return total;
}

pub fn main() void {
    const nums = [5]i32{10, 20, 30, 40, 50};
    const result = sum(&nums);
    std.debug.print("sum is: {d}\n", .{result});
}

Program #6: Error handling

const std = @import("std");

fn divide(a: i32, b: i32) !i32 {
    if (b == 0) return error.DivisionByZero;
    if (a > 1000 or b > 1000) return error.TooBig;
    return @divTrunc(a, b);
}

pub fn main() !void {
    const a: i32 = 22;
    const b: i32 = 3;

    const result = divide(a, b) catch |err| {
        std.debug.print("the system is down: {}\n", .{err});
        return;
    };
    std.debug.print("{} divided by {} is: {}\n", .{ a, b, result });
}
May 25, 2026

Session 4: Structs

Concepts

Program #7: Structs with methods

const std = @import("std");

pub fn main() void {
    var kid1 = Kid{
        .name = "CATS",
        .age = 11,
        .grade = 6,
    };

    const kid2 = Kid{
        .name = "Captain",
        .age = 10,
        .grade = 5,
    };

    const kids = [2]Kid{ kid1, kid2 };

    for (kids) |k| {
        k.describe();
    }

    std.debug.print("{s} was {}\n", .{ kid1.name, kid1.age });
    kid1.birthday();
    std.debug.print("Happy birthday, {s}! you're now {}!\n", .{ kid1.name, kid1.age });
}

const Kid = struct {
    name: []const u8,
    age: i32,
    grade: i32,

    pub fn describe(self: Kid) void {
        std.debug.print("{s} is {d} years old and in grade {d}\n", .{ self.name, self.age, self.grade });
    }

    pub fn birthday(self: *Kid) void {
        self.age += 1;
    }
};
May 26, 2026

Session 5: Optionals

Concepts

Program #8: Hey Sailor (structs, optionals, pointers)

const std = @import("std");
const print = std.debug.print;

const Sailor = struct {
    name: []const u8,
    age: i32,
    rank: i32,
    nickname: ?[]const u8 = null,

    pub fn describe(s: Sailor) void {
        print("{s} is {} years old and an E-{}. ", .{ s.name, s.age, s.rank });
        const nick = s.nickname orelse "no known nickname";
        print("Nickname: {s}.\n", .{nick});
    }

    pub fn promote(s: *Sailor) void {
        s.rank += 1;
        print("{s} was promoted to E-{}!\n", .{ s.name, s.rank });
    }
};

pub fn main() void {
    var sailor1 = Sailor{
        .name = "Jimmy Butta",
        .age = 19,
        .rank = 1,
        .nickname = "Sandy",
    };

    var sailor2 = Sailor{
        .name = "Billy Knives",
        .age = 17,
        .rank = 2,
    };

    const sailors = [2]*Sailor{ &sailor1, &sailor2 };

    for (sailors) |sailor| {
        sailor.describe();
    }

    sailor1.promote();
    sailor2.promote();
}
May 26, 2026

Session 6: CLI Tool, Word Counter

Concepts

Skill: Reading source when docs fail

# See what's inside a struct
grep -A 20 "pub const Init" /home/rrb/.zvm/0.16.0/lib/std/process.zig

# See all functions available on a type
grep "pub fn" /home/rrb/.zvm/0.16.0/lib/std/process/Args.zig

Program #9: Word counter CLI (first version)

const std = @import("std");

pub fn main(init: std.process.Init) void {
    var iter = init.minimal.args.iterate();
    _ = iter.next(); // skip binary path

    const sentence = iter.next() orelse return;

    var count: usize = 0;
    var words = std.mem.splitScalar(u8, sentence, ' ');

    while (words.next()) |word| {
        _ = word;
        count += 1;
    }
    std.debug.print("{d} words\n", .{count});
}

Usage

zig run 09-wordcount.zig -- "take off every zig"
4 words
May 30, 2026

Session 7: Word Counter Improvements

Concepts

const val = something orelse {
    std.debug.print("error message\n", .{});
    return;
};

Program #9: Word counter CLI (second version)

const std = @import("std");

pub fn main(init: std.process.Init) void {
    var iter = init.minimal.args.iterate();
    _ = iter.next();

    var had_args = false;
    var count: usize = 0;

    while (iter.next()) |sentence| {
        had_args = true;
        var words = std.mem.splitScalar(u8, sentence, ' ');
        while (words.next()) |word| {
            _ = word;
            count += 1;
        }
    }

    if (!had_args) {
        std.debug.print("usage: wordcount -- \"your sentence here\"\n", .{});
        return;
    }

    std.debug.print("{d} words\n", .{count});
}

Usage

zig run 09-wordcount.zig -- "take off every zig"
4 words

zig run 09-wordcount.zig -- take off every zig
4 words

zig run 09-wordcount.zig
usage: wordcount -- "your sentence here"
May 30, 2026

Session 8: File I/O, Line Counter

Concepts

Program #10: Line counter CLI

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    var iter = init.minimal.args.iterate();
    _ = iter.next(); // skip binary path

    // get filename from args
    const filename = iter.next() orelse return;

    // open file
    const file = try std.Io.Dir.cwd().openFile(
        init.io,
        filename,
        .{},
    );
    defer file.close(init.io);

    // read file contents
    var buffer: [4096]u8 = undefined;
    var r = std.Io.File.Reader.init(file, init.io, &buffer);
    const contents = try r.interface.allocRemaining(init.gpa, .unlimited);
    defer init.gpa.free(contents);

    // count lines
    var lines = std.mem.splitScalar(u8, contents, '\n');
    var count: usize = 0;

    while (lines.next()) |line| {
        _ = line;
        count += 1;
    }
    std.debug.print("{d} lines\n", .{count});
}

Usage

zig run 10-linecounter.zig -- warnings.csv
39 lines
May 31, 2026

Session 9: CSV Lookup Tool

Concepts

Program #11: Weather warning lookup CLI

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    var iter = init.minimal.args.iterate();
    _ = iter.next();

    const warning_code = iter.next() orelse {
        std.debug.print("usage: warnings <weather_code>\n", .{});
        return;
    };

    const filename = "warnings.csv";
    const file = try std.Io.Dir.cwd().openFile(
        init.io,
        filename,
        .{},
    );
    defer file.close(init.io);

    var buffer: [4096]u8 = undefined;
    var reader = std.Io.File.Reader.init(file, init.io, &buffer);

    const max_file_size: i32 = 1024 * 1024;
    const contents = try reader.interface.allocRemaining(init.gpa, .limited(max_file_size));
    defer init.gpa.free(contents);

    var rows = std.mem.splitScalar(u8, contents, '\n');

    while (rows.next()) |row| {
        var fields = std.mem.splitScalar(u8, row, ',');
        const code = fields.next();
        const warning = fields.next();
        if (std.mem.eql(u8, code orelse continue, warning_code)) {
            std.debug.print("warning: {?s}\n", .{warning});
            return;
        }
    }

    std.debug.print("invalid warning code: {s}\n", .{warning_code});
    return error.CodeNotFound;
}

Usage

zig run 11-lookup-file.zig -- 10
warning: Heavy Rain Advisory

zig run 11-lookup-file.zig -- 99
invalid warning code: 99
error: CodeNotFound

zig run 11-lookup-file.zig
usage: warnings <weather_code>
June 08, 2026

Session 10: Embedded CSV and Hardcoded Lookup Table

Concepts

Program 12: Weather warning lookup via embedded CSV

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    var iter = init.minimal.args.iterate();
    _ = iter.next();

    const warning_code = iter.next() orelse {
        std.debug.print("usage: warnings <weather_code>\n", .{});
        return;
    };

    const warnings_csv = @embedFile("warnings.csv");

    var rows = std.mem.splitScalar(u8, warnings_csv, '\n');

    while (rows.next()) |row| {
        var fields = std.mem.splitScalar(u8, row, ',');
        const code = fields.next();
        const warning = fields.next();
        if (std.mem.eql(u8, code orelse continue, warning_code)) {
            std.debug.print("warning: {?s}\n", .{warning});
            return;
        }
    }
    std.debug.print("invalid warning code: {s}\n", .{warning_code});
    return error.CodeNotFound;
}

Usage

zig run 12-lookup-embed.zig -- 10
warning: Heavy Rain Advisory

zig run 12-lookup-embed.zig -- 99
invalid warning code: 99
error: CodeNotFound

zig run 12-lookup-embed.zig
usage: warnings <weather_code>

Program 13: Weather warning lookup via hardcoded struct array

const std = @import("std");

const Warning = struct {
    code: []const u8,
    description: []const u8,

    pub fn describe(self: Warning) void {
        std.debug.print("warning: {s}\n", .{self.description});
    }
};

const warnings = [_]Warning{
    .{ .code = "0", .description = "Cancelled" },
    .{ .code = "2", .description = "Blizzard Warning" },
    .{ .code = "3", .description = "Heavy Rain Warning" },
    .{ .code = "4", .description = "Flood Warning" },
    .{ .code = "5", .description = "Storm Warning" },
    .{ .code = "6", .description = "Heavy Snow Warning" },
    .{ .code = "7", .description = "High Wave Warning" },
    .{ .code = "8", .description = "Storm Surge Warning" },
    .{ .code = "9", .description = "Landslide Warning (L3)" },
    .{ .code = "10", .description = "Heavy Rain Advisory" },
    .{ .code = "12", .description = "Heavy Snow Advisory" },
    .{ .code = "13", .description = "Wind & Snow Advisory" },
    .{ .code = "14", .description = "Thunder Advisory" },
    .{ .code = "15", .description = "Strong Wind Advisory" },
    .{ .code = "16", .description = "High Wave Advisory" },
    .{ .code = "17", .description = "Snowmelt Advisory" },
    .{ .code = "18", .description = "Flood Advisory" },
    .{ .code = "19", .description = "Storm Surge Advisory" },
    .{ .code = "20", .description = "Dense Fog Advisory" },
    .{ .code = "21", .description = "Dry Air Advisory" },
    .{ .code = "22", .description = "Avalanche Advisory" },
    .{ .code = "23", .description = "Low Temperature Advisory" },
    .{ .code = "24", .description = "Frost Advisory" },
    .{ .code = "25", .description = "Icing Advisory" },
    .{ .code = "26", .description = "Snow Accretion Advisory" },
    .{ .code = "27", .description = "Other Advisory" },
    .{ .code = "29", .description = "Landslide Advisory (L2)" },
    .{ .code = "32", .description = "Blizzard Emergency Warning" },
    .{ .code = "33", .description = "Heavy Rain Emergency Warning" },
    .{ .code = "35", .description = "Storm Emergency Warning" },
    .{ .code = "36", .description = "Heavy Snow Emergency Warning" },
    .{ .code = "37", .description = "High Wave Emergency Warning" },
    .{ .code = "38", .description = "Storm Surge Emergency Warning" },
    .{ .code = "39", .description = "Landslide Emergency Warning (L5)" },
    .{ .code = "43", .description = "Heavy Rain Danger Warning (L4)" },
    .{ .code = "48", .description = "Storm Surge Danger Warning (L4)" },
    .{ .code = "49", .description = "Landslide Danger Warning (L4)" },
};

pub fn main(init: std.process.Init) !void {
    var iter = init.minimal.args.iterate();
    _ = iter.next();

    const warning_code = iter.next() orelse {
        std.debug.print("usage: warnings <weather_code>\n", .{});
        return;
    };

    for (warnings) |w| {
        if (std.mem.eql(u8, w.code, warning_code)) {
            w.describe();
            return;
        }
    }
    std.debug.print("invalid warning code: {s}\n", .{warning_code});
    return error.CodeNotFound;
}

Usage

zig run 13-lookup-table.zig -- 10
warning: Heavy Rain Advisory

zig run 13-lookup-table.zig -- 99
invalid warning code: 99
error: CodeNotFound

zig run 13-lookup-table.zig
usage: warnings <weather_code>

June 08, 2026

Session 11: HTTP Client

Concepts

Program 14: HTTP client, fetch and print raw response

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;
    const io = init.io;

    // http client
    var client = std.http.Client{ .allocator = allocator, .io = io };
    defer client.deinit();

    // set url
    const area_url = "https://www.jma.go.jp/bosai/common/const/area.json";

    // response writer
    var response_body: std.Io.Writer.Allocating = .init(allocator);
    defer response_body.deinit();

    // fetch
    const response = try client.fetch(.{
        .method = .GET,
        .location = .{ .url = area_url },
        .response_writer = &response_body.writer,
    });
    std.debug.print("response status: {}\n", .{response});

    // print response body
    const body = response_body.written();
    std.debug.print("response body: {s}\n", .{body});
}

Usage

zig run 14-http-client.zig
response status: .{ .status = .ok }
response body: { ...raw JSON... }
June 14, 2026

Session 12: JSON Parsing

Concepts

Program 15: Fetch and parse area.json, print all offices

const std = @import("std");

const Office = struct {
    name: []const u8,
    enName: []const u8,
    officeName: []const u8,
    parent: []const u8,
    children: []const []const u8,
};

const AreaJson = struct {
    offices: std.json.ArrayHashMap(Office),
};

pub fn main(init: std.process.Init) !void {
    const allocator = init.gpa;
    const io = init.io;

    // http client
    var client = std.http.Client{ .allocator = allocator, .io = io };
    defer client.deinit();

    // set url
    const area_url = "https://www.jma.go.jp/bosai/common/const/area.json";

    // response writer
    var response_body: std.Io.Writer.Allocating = .init(allocator);
    defer response_body.deinit();

    // fetch
    const response = try client.fetch(.{
        .method = .GET,
        .location = .{ .url = area_url },
        .response_writer = &response_body.writer,
    });

    if (response.status != .ok) {
        std.debug.print("HTTP error {}\n", .{response.status});
        return;
    }

    // parse json
    const parsed = try std.json.parseFromSlice(
        AreaJson,
        allocator,
        response_body.written(),
        .{ .ignore_unknown_fields = true },
    );
    defer parsed.deinit();

    // iterate entries
    var iter = parsed.value.offices.map.iterator();
    while (iter.next()) |entry| {
        std.debug.print("{s}  {s} ({s})\n", .{
            entry.key_ptr.*,
            entry.value_ptr.name,
            entry.value_ptr.enName,
        });
    }
}

Usage

zig run 15-area-offices.zig
011000  宗谷地方 (Soya)
012000  上川・留萌地方 (Kamikawa Rumoi)
...
474000  八重山地方 (Yaeyama)