LEARN ZIG DEEP DIVE
Learn Zig: From Zero to Systems Programming Master
Goal: Deeply understand the Zig programming language—from its unique
comptimeand error handling to its explicit memory management and seamless C interoperability, empowering you to write robust, efficient, and clear low-level software.
Why Learn Zig?
Zig is a modern systems programming language designed to be a “better C” while offering a compelling alternative to Rust. It prioritizes simplicity, explicitness, and performance, giving developers fine-grained control without the historical baggage of C or the steep learning curve of Rust’s borrow checker.
After completing these projects, you will:
- Master
comptimefor powerful, compile-time metaprogramming. - Handle memory explicitly and safely using allocators.
- Write robust code with Zig’s clear error-handling model.
- Leverage Zig’s built-in build system to manage complex projects and cross-compile with ease.
- Seamlessly interoperate with existing C libraries.
- Write high-performance, concurrent applications with
async/await.
Core Concept Analysis
1. comptime: The Compile-Time Superpower
comptime allows you to execute Zig code at compile time. This isn’t just for constants; it’s for type generation, conditional compilation, and metaprogramming. It replaces C’s preprocessor, macros, and void* tricks with a single, type-safe feature.
┌───────────────────────────────┐ ┌──────────────────────────────┐
│ // Your Zig Code │ │ // What the Compiler Sees │
│ const T = u32; │ │ // After comptime evaluation │
│ const MyType = struct { │ │ const MyType = struct { │
│ a: T, │───▶ │ a: u32, │
│ b: if (comptime T == u32) │ │ b: bool, │
│ bool else f32, │ │ }; │
│ }; │ │ │
└───────────────────────────────┘ └──────────────────────────────┘
It unifies:
- Generics: A generic function is just a function that returns a type at
comptime. - Configuration:
build.zigusescomptimeto configure your build. - Metaprogramming: Introspect and generate types and functions.
2. Explicit Memory Management with Allocators
Zig gives you manual memory control, but with a crucial improvement: the allocator is a parameter. There are no hidden allocations. If a function needs to allocate memory, it must ask for an Allocator object.
┌──────────────────────────────────────────────┐
│ Your Application │
│ │
│ fn doWork(alloc: std.mem.Allocator) !void { │
│ var list = ArrayList(u8).init(alloc); │
│ try list.append('a'); │
│ } │
└──────────────────────────────────────────────┘
▲
│ Pass allocator instance
┌─────────────────┴─────────────────┐
│ │
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Arena │ │ Page │ │ Testing │
│ Allocator │ │ Allocator │ │ Allocator │
└───────────────┘ └───────────────┘ └───────────────┘
(Fast, for a (Standard OS (Tracks leaks
single frame) page alloc) during tests)
This makes code more flexible and transparent about memory usage.
3. Pragmatic Error Handling
Zig uses error unions (!T) to handle errors. A function that can fail returns MyError!MyType, which is either a MyError enum or a MyType value. This is checked at compile time.
// A function that can fail
fn mightFail() !u32 {
if (bad_condition) return error.SomethingWentWrong;
return 1337;
}
// Handling the error
const value = mightFail() catch |err| {
// Handle the error `err`
return 0;
};
// Or, propagating it up
const value = try mightFail();
This is more robust than C’s error codes and less verbose than Rust’s match statements on Result types.
4. The Integrated Build System
Your project’s build process is defined in a build.zig file, which is just Zig code. This eliminates the need for Makefiles, CMake, or other external tools.
┌──────────────────────────────────────────────┐
│ build.zig │
│ │
│ const exe = b.addExecutable("my-app", ...); │
│ exe.linkSystemLibrary("c"); │
│ exe.setTarget(.{ .cpu_arch = .aarch64 }); │
│ exe.install(); │
└──────────────────────────────────────────────┘
│
▼ `zig build`
┌──────────────────────────────────────────────┐
│ Cross-Compiled Binary │
│ (e.g., for ARM64 Linux from x86 Mac) │
└──────────────────────────────────────────────┘
Cross-compilation is a first-class feature, making it trivial to build for different platforms.
Project List
These projects are designed to build your Zig knowledge from the ground up, focusing on real-world applications of its core features.
Project 1: Custom Allocator
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C, Rust
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Memory Management
- Software or Tool: A custom memory allocator
- Main Book: Zig Docs - std.mem.Allocator
What you’ll build: A set of custom memory allocators, including a simple “bump” allocator and a fixed-size “pool” allocator, that adhere to Zig’s std.mem.Allocator interface.
Why it teaches Zig: This project forces you to confront Zig’s most fundamental concept: explicit memory management. You’ll understand how and why allocators are passed around everywhere in idiomatic Zig code.
Core challenges you’ll face:
- Implementing the Allocator VTable → maps to understanding function pointers and interfaces
- Managing memory blocks → maps to pointer arithmetic and byte-level manipulation
- Handling alignment → maps to
@alignOf,@ptrToInt, and bit-masking - Integrating with
std.ArrayList→ maps to using your allocator with standard library types
Key Concepts:
- Allocator Interface: Zig Documentation -
std.mem.Allocator - Pointer Arithmetic: “Low-Level Programming” by Igor Zhirkov, Ch. 4
- Memory Alignment: “Computer Systems: A Programmer’s Perspective”, Ch. 3
Difficulty: Intermediate Time estimate: Weekend Prerequisites: Basic understanding of pointers and memory layouts.
Real world outcome: You’ll have a custom allocator that you can plug into any Zig program. You can visually demonstrate its behavior by printing memory addresses and seeing how they are “bumped” or reused from a pool.
$ zig run main.zig
Bump Allocator:
Allocated 16 bytes at 0x7ffee...00
Allocated 32 bytes at 0x7ffee...10
Resetting arena...
Allocated 16 bytes at 0x7ffee...00 (same address as before)
Pool Allocator:
Allocated object at 0x7ffee...40
Allocated object at 0x7ffee...50
Freed object at 0x7ffee...40
Allocated object at 0x7ffee...40 (reused memory)
Implementation Hints: A bump allocator is the simplest. You start with a large buffer of memory.
allocFn: It just returns a pointer to the “next” available spot and “bumps” the pointer up by the requested size.resizeFn: It can only grow the last allocation if there’s space. Otherwise, it fails.freeFn: It does nothing! Memory is freed all at once by resetting the allocator.
Learning milestones:
- A bump allocator works → You understand the basic allocator interface.
- A pool allocator works → You can manage a free list for fixed-size blocks.
- Your allocators pass a test suite → You’ve handled edge cases like alignment and zero-sized allocations.
Project 2: A comptime-Powered Vector Library
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C++, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 2: Intermediate
- Knowledge Area: Metaprogramming / Linear Algebra
- Software or Tool: A 2D/3D math library
- Main Book: Zig Docs - Comptime
What you’ll build: A generic vector math library (Vec<N, T>) where N (dimensions) and T (element type) are compile-time parameters.
Why it teaches Zig: This is the ideal project for learning comptime. You’ll build a single, flexible Vec struct that can represent a Vec2(f32), Vec3(i64), or even a Vec10(u8) without macros or code duplication.
Core challenges you’ll face:
- Creating a generic
Vectype → maps to writing a function that returns a struct type atcomptime - Implementing generic functions (
dot,crossetc.) → maps to usingcomptimeloops and conditional logic - Accessing components via
.x,.y,.z→ maps to using@fieldParentPtrandcomptimeto generate fields - Operator overloading → maps to implementing
add,mulfunctions in the struct
Key Concepts:
- Generic Data Structures: Zig Documentation on
comptime - Type Functions: zig.news - “What is a Type?”
- Vector Math: “3D Math Primer for Graphics and Game Development” by Fletcher Dunn
Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Basic familiarity with structs and functions.
Real world outcome: You’ll have a type-safe, high-performance math library.
// Your code
const Vec2f = Vec(2, f32);
const Vec3i = Vec(3, i32);
var v1 = Vec2f{.x = 1.0, .y = 2.0};
var v2 = Vec2f{.x = 3.0, .y = 4.0};
var v3 = v1.add(v2); // {.x = 4.0, .y = 6.0}
var cross_prod = Vec3i{1,0,0}.cross(Vec3i{0,1,0}); // {0,0,1}
// This would be a compile error:
// var error = v1.add(cross_prod);
Implementation Hints:
Your main Vec function will look something like this:
pub fn Vec(comptime N: u32, comptime T: type) type {
return struct {
data: [N]T,
// ... methods go here
};
}
Inside the struct, write a dot product function that uses a comptime for loop to iterate over the elements 0 to N-1.
Learning milestones:
- You can create
Vec(N, T)→ You understand type-returning functions. - Generic
dotproduct works → You can usecomptimeloops. .x, .y, .zaccessors compile → You’ve mastered struct composition.- The library is used in a simple graphics demo → You’ve built something practical.
Project 3: A grep Clone
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C, Go, Rust
- Coolness Level: Level 2: Practical but Forgettable
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 1: Beginner
- Knowledge Area: File I/O / CLI Tools
- Software or Tool: A command-line search utility
- Main Book: “The Zig Programming Language” (Book) - Chapter on I/O
What you’ll build: A simplified version of the grep command-line tool that searches for a pattern in files and prints matching lines.
Why it teaches Zig: This is a perfect “first project.” It teaches file handling, command-line arguments, error handling (try/catch), and working with slices ([]u8), all while using a custom allocator.
Core challenges you’ll face:
- Parsing command-line arguments → maps to using
std.process.args - Reading files line by line → maps to buffered I/O and handling
\n - Handling I/O errors robustly → maps to using
tryandcatchfor file-not-found, permission-denied, etc. - Managing memory for file contents → maps to using an allocator to hold lines or the whole file
Key Concepts:
- Error Handling: The
tryandcatchkeywords in the Zig documentation. - File I/O:
std.fsandstd.iomodules. - Argument Parsing:
std.process.argsorstd.options.
Difficulty: Beginner Time estimate: Weekend Prerequisites: None. This is a great starting point.
Real world outcome: A working command-line tool you can use in your daily workflow.
$ cat file.txt
hello world
zig is cool
goodbye world
$ ./zig-grep world file.txt
hello world
goodbye world
Implementation Hints:
- Use
std.process.args()to get the pattern and file path(s). Remember to use an allocator. - Open the file using
std.fs.cwd().openFile(). Usetryto handle potential errors. - Create a buffered reader with
std.io.bufferedReader()to efficiently read the file. - Loop and use
reader.readUntilDelimiter()to get each line. - Use
std.mem.indexOfto check if the line contains the pattern. - If it does, print the line to standard output.
- Don’t forget to
deferclosing the file.
Learning milestones:
- Your program searches one file for a string → You understand basic file I/O.
- It handles file-not-found errors gracefully → You’ve mastered
try/catch. - It can read from standard input if no file is given → You understand abstracting over readers.
- It’s as fast as the system
grepfor simple cases → You’ve written efficient I/O code.
Project 4: Build a zig build Script for a C Project
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: (Build systems like Make, CMake)
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 3. The “Service & Support” Model
- Difficulty: Level 2: Intermediate
- Knowledge Area: Build Systems / C Interoperability
- Software or Tool: Zig as a C compiler/linker
- Main Book: Zig Docs - Build System
What you’ll build: A build.zig file that compiles an existing C project (like a small library or command-line tool) and cross-compiles it for another architecture (e.g., ARM).
Why it teaches Zig: This project highlights one of Zig’s killer features: its build system and C tooling. You’ll learn that zig is not just a language compiler but a full-featured C/C++ compiler and linker that makes cross-compilation trivial.
Core challenges you’ll face:
- Setting up the build script → maps to understanding the
buildfunction andBuilderAPI - Adding C source files → maps to using
addExecutableoraddStaticLibrarywith.cfiles - Linking against C standard library → maps to using
linkSystemLibrary("c") - Cross-compiling → maps to setting a different target with
.setTarget()
Key Concepts:
- Zig Build System: Zig documentation and
zig init-exeexample. - Cross Compilation: zig.news - “Cross-compilation with Zig is a joy”
- C Integration:
b.addStaticLibraryand related functions inbuild.zig.
Difficulty: Intermediate Time estimate: Weekend Prerequisites: Project 3, basic knowledge of how C code is compiled.
Real world outcome: You can take a simple C project that uses Makefiles and compile it for Windows, macOS, and Linux on ARM and x86 with a single command, from a single machine.
# On your x86-64 Mac:
$ zig build -Dtarget=aarch64-linux-gnu
# In zig-cache/bin:
$ file my-c-app
my-c-app: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked...
Implementation Hints:
- Find a simple C project (e.g., sokoban.c).
- In the same directory, run
zig init-exe. This will create abuild.zig. - Modify
build.zig. Instead ofmain.zig, giveexe.addCSourceFile()the path to the C source file. - Add
exe.linkSystemLibrary("c")to link against libc. - Run
zig build. It should compile the C code and produce an executable. - Now, add a cross-compilation target option to the build script to enable commands like the one above.
Learning milestones:
zig buildcompiles a single C file → You understand the basics of the build system.- You can cross-compile the C program for another OS/Arch → You’ve unlocked Zig’s superpower.
- Your
build.zigcan build the C code as both a static library and an executable → You understand build artifacts. - You can call a function from the C code within
main.zig→ You’ve mastered C interoperability.
Project 5: Game Boy Emulator
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C, C++, Rust
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Emulation / Low-Level Programming
- Software or Tool: A CPU emulator
- Main Book: Pan Docs (Game Boy technical reference)
What you’ll build: An emulator for the original Nintendo Game Boy. It will read ROM files, execute CPU instructions, and (eventually) render graphics.
Why it teaches Zig: Emulators are the ultimate low-level programming exercise. You’ll need performance, precise control over integer types and bitwise operations, and a clean way to manage the CPU state machine. Zig’s explicitness and performance are perfect for this.
Core challenges you’ll face:
- Implementing the Z80-like CPU → maps to a giant
switchstatement, bitwise operations, and integer overflow handling (@addWithOverflow) - Memory-Mapped I/O (MMIO) → maps to managing reads/writes to specific memory addresses that control hardware like the PPU (Pixel Processing Unit)
- Decoding opcodes → maps to complex bit masking and shifting
- Managing the CPU state machine → maps to structs, unions, and careful state management
Key Concepts:
- CPU Emulation: “Game Boy Emulation in Rust” (blog series) - provides the concepts, you apply them in Zig.
- Bitwise Operations: Zig documentation on bitwise operators (
|,&,^,<<,>>). - Integer Handling: Zig’s arbitrary-bit-width integers (e.g.,
u1,i7) and overflow builtins.
Difficulty: Advanced Time estimate: 1 month+ Prerequisites: Project 1, 2, and 4. Strong understanding of computer architecture.
Real world outcome: You will be able to run a real Game Boy ROM (like Tetris or Dr. Mario) and see it running on your computer. You will have a deep, visceral understanding of how computers work at a fundamental level.
$ zig run main.zig -- roms/tetris.gb
# A window opens showing the Tetris title screen
Implementation Hints:
- Start with the CPU. Create a
CPUstruct to hold the registers (a,b,c,d,e,h,l,sp,pc). - Create a
[65536]u8array for the memory bus. - Write the main loop:
fetch -> decode -> execute. - Implement one instruction at a time, starting with
NOPandLD. Use aswitchon the opcode byte. - Use a testing framework to write a test for each instruction you implement, checking that the registers and flags are correct.
- Once you have a good portion of the CPU, start implementing the PPU to get graphics on the screen.
Learning milestones:
- Your emulator runs the boot ROM successfully → You’ve implemented the core CPU instructions.
- You can see the Nintendo logo appear → Your PPU and memory mapping are starting to work.
- Tetris is playable → You’ve handled interrupts, timers, and input.
- Your emulator passes Blargg’s instruction timing tests → You’ve achieved cycle-accurate emulation.
Project 6: An async HTTP Server
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: Go, Rust, Node.js
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: Networking / Concurrency
- Software or Tool: A high-performance web server
- Main Book: Zig Docs - Async
What you’ll build: A simple, concurrent HTTP/1.1 server from scratch that can serve static files and handle multiple simultaneous connections using Zig’s async/await framework.
Why it teaches Zig: This project is the best way to learn Zig’s approach to concurrency. You’ll see how async functions are color-blind (they are just functions that return a Frame), how suspend and resume work, and how to build a scalable I/O-bound application.
Core challenges you’ll face:
- Accepting TCP connections → maps to using
std.net.tcpListenandacceptin a loop - Handling multiple clients concurrently → maps to spawning an
asyncframe for each connection - Parsing HTTP requests → maps to manual string and buffer manipulation
- Managing the event loop → maps to understanding how
suspendandresumeinteract with the evented I/O system
Key Concepts:
- Async Functions: Zig Documentation chapter on
async. - Event-driven I/O: “Operating Systems: Three Easy Pieces”, Ch. 6 (Concurrency)
- TCP Sockets: “The Sockets Networking API” by W. Richard Stevens
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 3. A basic understanding of HTTP.
Real world outcome:
A web server you can run locally. You can point your web browser to http://localhost:8080/file.html and see it serve the file. Benchmarking tools like wrk will show it handling thousands of concurrent connections.
$ zig build run
Listening on http://127.0.0.1:8080
Handling connection from 127.0.0.1:54321
-> GET /index.html HTTP/1.1
<- HTTP/1.1 200 OK
Handling connection from 127.0.0.1:54322
-> GET /style.css HTTP/1.1
<- HTTP/1.1 200 OK
Implementation Hints:
- Create a main loop that calls
server.accept(). - For each accepted connection,
async handleConnection(stream). - Inside
handleConnection, create a buffered reader for the stream. - Read the request line by line until you hit an empty line (
\r\n). - Parse the first line to get the method (GET) and path.
- Use the path to open a local file. If it exists, send back a “200 OK” header followed by the file contents. Otherwise, send a “404 Not Found”.
- Use
suspendandresumeblocks to manage theasyncstate machine explicitly.
Learning milestones:
- Your server accepts a single connection and serves one file → You understand basic TCP sockets.
- It handles multiple connections concurrently → You’ve mastered
async/await. - It correctly serves different file types (HTML, CSS, images) → You understand HTTP headers.
- The server can handle 10,000 concurrent connections (C10k problem) → You’ve written a high-performance, event-driven server.
Project 7: comptime-Reflecting JSON Parser
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: Go, Rust
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Parsing / Metaprogramming
- Software or Tool: A data serialization library
- Main Book: “Crafting Interpreters” by Robert Nystrom (for parsing fundamentals)
What you’ll build: A high-performance JSON parsing library that can deserialize JSON strings directly into native Zig structs without requiring boilerplate code from the user.
Why it teaches Zig: This project is a masterclass in advanced comptime. You will use it to “reflect” on a struct type, find its fields and types, and generate a custom parser for that specific struct at compile time, resulting in extreme performance.
Core challenges you’ll face:
- Parsing the JSON format → maps to building a recursive descent parser or state machine
- Generic deserialization function
fromJson(T, json_string)→ maps to using@TypeOfandcomptime - Iterating over struct fields at compile time → maps to using
@typeInfoandcomptimeloops - Generating field-specific parsing logic → maps to
inline forloops and conditionalcomptimelogic to handle strings, numbers, bools, etc.
Key Concepts:
- Type Reflection:
std.meta.fieldsand@typeInfo. - Parsing Techniques: “Crafting Interpreters”, Part II - A good mental model for parsing.
comptimeCode Generation: Zig SHOWTIME - Ep. 2 - Comptime JSON
Difficulty: Advanced
Time estimate: 2-3 weeks
Prerequisites: Project 2. Strong grasp of comptime.
Real world outcome:
A library that provides a magical, high-performance fromJson function.
const User = struct {
id: u64,
name: []const u8,
is_active: bool,
};
const json_data =
\\{
\\ "id": 123,
\\ "name": "Alice",
\\ "is_active": true
\\}
;
// Your library does the magic here!
var user = try my_json_lib.fromJson(User, json_data, allocator);
std.debug.print("{any}\n", .{user});
// Output:
// main.User{ .id = 123, .name = "Alice", .is_active = true }
Implementation Hints:
- Start with a simple, non-generic JSON parser that produces a
JsonValuetagged union. - Create your main function
fromJson(comptime T: type, data: []const u8, alloc: Allocator) !T. - Inside, use
@typeInfo(T).Struct.fieldsto get an array of all the fields in the structT. - Use an
inline forloop to iterate over the fields at compile time. - For each field, generate parsing logic based on its type (
field.type). If it’s au64, parse a number. If it’s[]const u8, parse a string. - This generates a highly specialized, non-generic parser for
Tat compile time, which is then used to parse the data.
Learning milestones:
- You can parse a JSON string into a generic
Valuetree → You have a working parser. fromJsonworks for a simple struct with one field → You’ve connectedcomptimereflection to the parser.- The parser handles nested structs and arrays of structs → You’ve implemented recursive type generation.
- Your parser is faster than a non-
comptimeversion → You’ve proven the benefits of compile-time code generation.
Project 8: Bare-metal “Hello World” on QEMU
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C, Assembly
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Operating Systems / Embedded
- Software or Tool: A minimal OS kernel
- Main Book: “Operating Systems: Three Easy Pieces” by Arpaci-Dusseau
What you’ll build: A freestanding Zig program that runs on bare metal (emulated by QEMU). It will not link libc or any OS libraries. Its only job is to print “Hello, world!” to the serial port.
Why it teaches Zig: This project teaches you what “freestanding” and “no_std” really mean. You’ll learn how Zig works without an underlying OS, how to interact with hardware directly via memory-mapped I/O, and how to write your own entry point (_start).
Core challenges you’ll face:
- Creating a freestanding build → maps to setting the target OS to
.freestandingand disablinglibclinking - Writing an entry point → maps to exporting a
_startfunction instead ofmain - Interacting with hardware → maps to writing to magical memory addresses (e.g.,
0x3F8for the serial port) usingvolatile - Setting up the QEMU environment → maps to learning QEMU command-line flags to emulate a specific machine
Key Concepts:
- Freestanding vs. Hosted: OSDev Wiki - Bare Bones
- Memory-Mapped I/O: “Making Embedded Systems” by Elecia White, Ch. 4
- Volatile Keyword: Zig Docs - volatile
Difficulty: Expert Time estimate: 1-2 weeks Prerequisites: Project 4. Familiarity with assembly concepts.
Real world outcome: You will run a command and see your “Hello, world!” message appear directly from an emulated machine that is running only your Zig code.
# Build the freestanding binary
$ zig build
# Run it in QEMU
$ qemu-system-x86_64 -serial stdio -kernel zig-out/bin/my-kernel
Hello, bare-metal world!
# QEMU exits
Implementation Hints:
- Set up your
build.zigto create a freestanding executable. The target will be something likex86_64-freestanding-none. - In your
main.zig, removemainand createpub export fn _start() -> !. This function cannot return. - Define a pointer to the serial port’s data register:
const SERIAL_PORT = @intToPtr(*volatile u8, 0x3f8);. - Write a simple loop that writes each character of your “Hello” string to this pointer.
- In your
_startfunction, call your serial print function, then enter an infinite loopwhile (true) {}.
Learning milestones:
- Your code compiles as a freestanding binary → You’ve configured the build system correctly.
- QEMU boots your kernel without crashing → Your entry point and basic setup are correct.
- “Hello, world!” appears on the serial console → You have successfully mastered memory-mapped I/O.
- You create a simple VGA driver to print to the screen → You’ve expanded your hardware interaction skills.
Project 9: A ncurses-like TUI Library
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: C Interoperability / TUI
- Software or Tool: A terminal user interface library
- Main Book: “The C Programming Language” by K&R (for understanding C idioms)
What you’ll build: A high-level, idiomatic Zig wrapper around a C library like ncurses or BearLibTerminal. Your library will expose a safe, Zig-style API for building terminal applications.
Why it teaches Zig: This is the definitive C interoperability project. You will use @cImport to directly include C headers, translate C-style APIs (return codes, pointer outputs) into Zig-style APIs (error unions, slices), and manage resource lifetimes (init/deinit).
Core challenges you’ll face:
- Importing C headers → maps to using
@cImportand understanding how C types map to Zig types - Wrapping C functions → maps to creating a Zig function that calls the C function and translates its inputs/outputs
- Handling C strings → maps to converting between null-terminated
[:0]u8and Zig slices[]const u8 - Managing library state → maps to creating an
initfunction that calls the C setup function and adeinitthat cleans up
Key Concepts:
- C Interoperability: Zig Docs -
@cImport - String Conversion:
std.mem.sliceToandstd.mem.span. - API Design: “API Design Patterns” by JJ Geewax.
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 4. Basic knowledge of C.
Real world outcome: A library that lets you or others build complex TUIs in Zig with a clean, safe API, abstracting away the unsafe C details.
// Your library's API
var tui = try MyTui.init(allocator);
defer tui.deinit();
try tui.print(10, 5, "Hello from Zig!", .{.fg = .white, .bg = .blue});
try tui.refresh();
_ = tui.readChar();
Implementation Hints:
- In
build.zig, findncursesusingb.pkg_config.findor similar and link it usingexe.linkSystemLibrary(). - In your Zig code, use
@cImportto bring in thencurses.hheader. - Create a
Tuistruct. Theinitfunction will callinitscr()from C and store any necessary state. Thedeinitfunction will callendwin(). - Create a
printfunction in your struct that takes a Zig slice ([]const u8). Inside, you’ll need to allocate a null-terminated C string, copy the slice contents, and then call the Cmvprintwfunction. - Wrap other
ncursesfunctions, always translating error codes and C types into idiomatic Zig.
Learning milestones:
- You can initialize ncurses and clear the screen → You have basic C linking and
@cImportworking. - You can print text to the screen → You’ve mastered C string conversion.
- Your library exposes a safe API for colors and attributes → You’re designing a good abstraction.
- You build a simple TUI application (e.g., a file viewer) using your own library → Your API is practical and usable.
Final Project: A Simple 2D Game Engine
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C++, Rust, Go
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 4: Expert
- Knowledge Area: Game Development / Systems Integration
- Software or Tool: A complete game engine
- Main Book: “Game Engine Architecture” by Jason Gregory
What you’ll build: A small, from-scratch 2D game engine that integrates all the concepts you’ve learned. It will feature a main loop, rendering (via C library), an entity-component system (ECS), and scripting.
Why it’s the final project: This project synthesizes everything. You will use custom allocators for game state, a comptime-based vector library for math, async I/O for loading assets, Zig’s build system for compilation, and C interop for graphics and sound.
Core challenges you’ll face:
- Engine Architecture → maps to designing the main loop, state management, and subsystem interactions
- Rendering Abstraction → maps to wrapping a C library like SDL or Raylib with a clean Zig API
- Entity-Component System → maps to advanced data structures and
comptimefor efficient, data-oriented design - Asset Loading → maps to file I/O,
asyncoperations, and parsing simple formats (like PNG or WAV)
Key Concepts:
- Game Loop: “Game Programming Patterns” by Robert Nystrom, Ch. 1.
- Entity-Component-System: Zig ECS examples on GitHub.
- C Graphics Libs: Raylib or SDL documentation.
Difficulty: Expert Time estimate: 1-2 months+ Prerequisites: All previous projects.
Real world outcome: A functional game engine that can run a simple game like Pong or a side-scroller. You’ll have a powerful portfolio piece and a deep, holistic understanding of how to build complex systems in Zig.
Project Comparison Table
| Project | Difficulty | Time | Core Zig Concept | Fun Factor |
|—|—|—|—|—|
| A grep Clone | Beginner | Weekend | Error Handling, I/O | ★★★☆☆ |
| Custom Allocator | Intermediate | Weekend | Memory Management | ★★★★☆ |
| comptime Vector Lib | Intermediate | 1-2 weeks | comptime | ★★★★☆ |
| zig build for C | Intermediate | Weekend | Build System, C Interop | ★★★☆☆ |
| async HTTP Server | Advanced | 1-2 weeks | async/await | ★★★★☆ |
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C, Assembly
- Coolness Level: Level 5: Pure Magic (Super Cool)
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Operating Systems / Embedded
- Software or Tool: A minimal OS kernel
- Main Book: “Operating Systems: Three Easy Pieces” by Arpaci-Dusseau
What you’ll build: A freestanding Zig program that runs on bare metal (emulated by QEMU). It will not link libc or any OS libraries. Its only job is to print “Hello, world!” to the serial port.
Why it teaches Zig: This project teaches you what “freestanding” and “no_std” really mean. You’ll learn how Zig works without an underlying OS, how to interact with hardware directly via memory-mapped I/O, and how to write your own entry point (_start).
Core challenges you’ll face:
- Creating a freestanding build → maps to setting the target OS to
.freestandingand disablinglibclinking - Writing an entry point → maps to exporting a
_startfunction instead ofmain - Interacting with hardware → maps to writing to magical memory addresses (e.g.,
0x3F8for the serial port) usingvolatile - Setting up the QEMU environment → maps to learning QEMU command-line flags to emulate a specific machine
Key Concepts:
- Freestanding vs. Hosted: OSDev Wiki - Bare Bones
- Memory-Mapped I/O: “Making Embedded Systems” by Elecia White, Ch. 4
- Volatile Keyword: Zig Docs - volatile
Difficulty: Expert Time estimate: 1-2 weeks Prerequisites: Project 4. Familiarity with assembly concepts.
Real world outcome: You will run a command and see your “Hello, world!” message appear directly from an emulated machine that is running only your Zig code.
# Build the freestanding binary
$ zig build
# Run it in QEMU
$ qemu-system-x86_64 -serial stdio -kernel zig-out/bin/my-kernel
Hello, bare-metal world!
# QEMU exits
Implementation Hints:
- Set up your
build.zigto create a freestanding executable. The target will be something likex86_64-freestanding-none. - In your
main.zig, removemainand createpub export fn _start() -> !. This function cannot return. - Define a pointer to the serial port’s data register:
const SERIAL_PORT = @intToPtr(*volatile u8, 0x3f8);. - Write a simple loop that writes each character of your “Hello” string to this pointer.
- In your
_startfunction, call your serial print function, then enter an infinite loopwhile (true) {}.
Learning milestones:
- Your code compiles as a freestanding binary → You’ve configured the build system correctly.
- QEMU boots your kernel without crashing → Your entry point and basic setup are correct.
- “Hello, world!” appears on the serial console → You have successfully mastered memory-mapped I/O.
- You create a simple VGA driver to print to the screen → You’ve expanded your hardware interaction skills.
Project 9: A ncurses-like TUI Library
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C, Rust
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 3: Advanced
- Knowledge Area: C Interoperability / TUI
- Software or Tool: A terminal user interface library
- Main Book: “The C Programming Language” by K&R (for understanding C idioms)
What you’ll build: A high-level, idiomatic Zig wrapper around a C library like ncurses or BearLibTerminal. Your library will expose a safe, Zig-style API for building terminal applications.
Why it teaches Zig: This is the definitive C interoperability project. You will use @cImport to directly include C headers, translate C-style APIs (return codes, pointer outputs) into Zig-style APIs (error unions, slices), and manage resource lifetimes (init/deinit).
Core challenges you’ll face:
- Importing C headers → maps to using
@cImportand understanding how C types map to Zig types - Wrapping C functions → maps to creating a Zig function that calls the C function and translates its inputs/outputs
- Handling C strings → maps to converting between null-terminated
[:0]u8and Zig slices[]const u8 - Managing library state → maps to creating an
initfunction that calls the C setup function and adeinitthat cleans up
Key Concepts:
- C Interoperability: Zig Docs -
@cImport - String Conversion:
std.mem.sliceToandstd.mem.span. - API Design: “API Design Patterns” by JJ Geewax.
Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 4. Basic knowledge of C.
Real world outcome: A library that lets you or others build complex TUIs in Zig with a clean, safe API, abstracting away the unsafe C details.
// Your library's API
var tui = try MyTui.init(allocator);
defer tui.deinit();
try tui.print(10, 5, "Hello from Zig!", .{.fg = .white, .bg = .blue});
try tui.refresh();
_ = tui.readChar();
Implementation Hints:
- In
build.zig, findncursesusingb.pkg_config.findor similar and link it usingexe.linkSystemLibrary(). - In your Zig code, use
@cImportto bring in thencurses.hheader. - Create a
Tuistruct. Theinitfunction will callinitscr()from C and store any necessary state. Thedeinitfunction will callendwin(). - Create a
printfunction in your struct that takes a Zig slice ([]const u8). Inside, you’ll need to allocate a null-terminated C string, copy the slice contents, and then call the Cmvprintwfunction. - Wrap other
ncursesfunctions, always translating error codes and C types into idiomatic Zig.
Learning milestones:
- You can initialize ncurses and clear the screen → You have basic C linking and
@cImportworking. - You can print text to the screen → You’ve mastered C string conversion.
- Your library exposes a safe API for colors and attributes → You’re designing a good abstraction.
- You build a simple TUI application (e.g., a file viewer) using your own library → Your API is practical and usable.
Final Project: A Simple 2D Game Engine
- File:
LEARN_ZIG_DEEP_DIVE.md - Main Programming Language: Zig
- Alternative Programming Languages: C++, Rust, Go
- Coolness Level: Level 4: Hardcore Tech Flex
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 4: Expert
- Knowledge Area: Game Development / Systems Integration
- Software or Tool: A complete game engine
- Main Book: “Game Engine Architecture” by Jason Gregory
What you’ll build: A small, from-scratch 2D game engine that integrates all the concepts you’ve learned. It will feature a main loop, rendering (via C library), an entity-component system (ECS), and scripting.
Why it’s the final project: This project synthesizes everything. You will use custom allocators for game state, a comptime-based vector library for math, async I/O for loading assets, Zig’s build system for compilation, and C interop for graphics and sound.
Core challenges you’ll face:
- Engine Architecture → maps to designing the main loop, state management, and subsystem interactions
- Rendering Abstraction → maps to wrapping a C library like SDL or Raylib with a clean Zig API
- Entity-Component System → maps to advanced data structures and
comptimefor efficient, data-oriented design - Asset Loading → maps to file I/O,
asyncoperations, and parsing simple formats (like PNG or WAV)
Key Concepts:
- Game Loop: “Game Programming Patterns” by Robert Nystrom, Ch. 1.
- Entity-Component-System: Zig ECS examples on GitHub.
- C Graphics Libs: Raylib or SDL documentation.
Difficulty: Expert Time estimate: 1-2 months+ Prerequisites: All previous projects.
Real world outcome: A functional game engine that can run a simple game like Pong or a side-scroller. You’ll have a powerful portfolio piece and a deep, holistic understanding of how to build complex systems in Zig.
Project Comparison Table
| Project | Difficulty | Time | Core Zig Concept | Fun Factor |
|---|---|---|---|---|
A grep Clone |
Beginner | Weekend | Error Handling, I/O | ★★★☆☆ |
| Custom Allocator | Intermediate | Weekend | Memory Management | ★★★★☆ |
comptime Vector Lib |
Intermediate | 1-2 weeks | comptime |
★★★★☆ |
zig build for C |
Intermediate | Weekend | Build System, C Interop | ★★★☆☆ |
async HTTP Server |
Advanced | 1-2 weeks | async/await |
★★★★☆ |
| TUI Library | Advanced | 1-2 weeks | C Interop, API Design | ★★★★☆ |
| JSON Parser | Advanced | 2-3 weeks | comptime, Reflection |
★★★★★ |
| Game Boy Emulator | Advanced | 1 month+ | Low-Level, Performance | ★★★★★ |
| Bare-metal “Hello” | Expert | 1-2 weeks | Freestanding, HW I/O | ★★★★★ |
| 2D Game Engine | Expert | 1-2 months+ | Systems Integration | ★★★★★ |
Recommendation
For a developer new to Zig:
- Start with Project 3: A
grepClone. It’s the fastest way to get comfortable with Zig’s syntax, file I/O, and error handling. - Move to Project 1: Custom Allocator. This will cement your understanding of Zig’s most critical feature: explicit memory management.
- Then, choose your path:
- If you love metaprogramming, tackle Project 2:
comptimeVector Lib. - If you’re interested in networking, try Project 6:
asyncHTTP Server. - If you want to master C integration, do Project 4:
zig buildfor C Project.
- If you love metaprogramming, tackle Project 2:
After these, you’ll be well-equipped to tackle any of the advanced or expert projects based on your interests.
Summary
| Project | Main Programming Language |
|---|---|
| Custom Allocator | Zig |
A comptime-Powered Vector Library |
Zig |
A grep Clone |
Zig |
Build a zig build Script for a C Project |
Zig |
| Game Boy Emulator | Zig |
An async HTTP Server |
Zig |
comptime-Reflecting JSON Parser |
Zig |
| Bare-metal “Hello World” on QEMU | Zig |
A ncurses-like TUI Library |
Zig |
| A Simple 2D Game Engine | Zig |