Getting started
Hello World
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello, {s}!\n", .{"World"});
}
Build and run:
zig build-exe hello.zig
./hello
Installation
# macOS
brew install zig
# Linux (tarball)
wget https://ziglang.org/download/latest
tar xf zig-linux-*.tar.xz
export PATH=$PATH:~/zig
# Windows (Scoop)
scoop install zig
Verify installation:
zig version # Show Zig version
zig zen # Display Zen of Zig
Variables
const x = 5; // Immutable (like const in Rust)
var y = 10; // Mutable
const z: i32 = 15; // Explicit type
// ⚠️ const means compile-time or runtime immutable
// NOT C's "const" (which is just a hint)
Basic types
Integers
// Signed integers
i8, i16, i32, i64, i128 // Fixed width
isize // Pointer-sized
// Unsigned integers
u8, u16, u32, u64, u128
usize // Pointer-sized
// Arbitrary bit-width ⚠️ Zig-specific!
i3, u7, i24, u47 // Any bit width
Floats
f16, f32, f64, f128 // IEEE-754 floats
const pi: f32 = 3.14;
const e: f64 = 2.71828;
Other primitives
bool // true or false
void // Zero-size type
noreturn // For functions that never return
type // Type of types
anyerror // Global error set
comptime_int // Compile-time integers
comptime_float // Compile-time floats
Functions
Basic functions
fn add(a: i32, b: i32) i32 {
return a + b;
}
// ⚠️ No function overloading (use generics)
Error unions
fn divide(a: i32, b: i32) !i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
// !i32 means "error union of error and i32"
// Like Result<i32, Error> in Rust
Multiple return values
fn divmod(a: i32, b: i32) struct { quot: i32, rem: i32 } {
return .{
.quot = @divTrunc(a, b),
.rem = @rem(a, b),
};
}
const result = divmod(10, 3);
// result.quot == 3, result.rem == 1
Generic functions
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
const x = max(i32, 5, 10); // x = 10
const y = max(f64, 3.14, 2.71); // y = 3.14
Error handling
Try and catch
// try unwraps or propagates error
const result = try divide(10, 2);
// catch handles error
const result = divide(10, 0) catch 0;
const result = divide(10, 0) catch |err| {
std.debug.print("Error: {}\n", .{err});
return err;
};
Error sets
const FileError = error{
NotFound,
PermissionDenied,
OutOfMemory,
};
fn readFile(path: []const u8) FileError![]u8 {
// ...
return error.NotFound;
}
Defer and errdefer
fn doWork() !void {
const file = try std.fs.cwd().openFile("data.txt", .{});
defer file.close(); // Always runs
const buf = try allocator.alloc(u8, 1024);
errdefer allocator.free(buf); // Runs only on error
// Work with file and buffer
try processData(buf);
}
// ⚠️ defer runs in reverse order (like C++ destructors)
Optionals
Optional types
const maybe_num: ?i32 = null; // Optional (like Option<i32>)
const value: ?i32 = 42;
// Unwrap with orelse
const x = maybe_num orelse 0; // x = 0
// Unwrap with if
if (maybe_num) |num| {
// num is unwrapped i32
std.debug.print("Value: {}\n", .{num});
}
Optional pointers
const ptr: ?*i32 = null;
// Check and unwrap
if (ptr) |p| {
p.* = 42;
}
// ⚠️ Non-optional pointers are NEVER null
// Use ?*T for nullable pointers
Combining optionals and errors
fn findUser(id: u32) !?User {
// Returns error, null, or User
const user = try database.query(id);
if (user.id == 0) return null;
return user;
}
// Unwrap both error and optional
if (findUser(42)) |user| {
// user is User
} else |err| {
// err is error
}
Memory management
Allocators
const std = @import("std");
// General purpose allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Arena (frees all at once)
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
// Fixed buffer
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
Allocation
// Allocate single item
const ptr = try allocator.create(i32);
defer allocator.destroy(ptr);
ptr.* = 42;
// Allocate slice
const slice = try allocator.alloc(i32, 100);
defer allocator.free(slice);
// Allocate with alignment
const aligned = try allocator.alignedAlloc(u8, 16, 256);
defer allocator.free(aligned);
Common allocators
| Allocator | Use Case | Free Strategy |
|---|---|---|
page_allocator |
Large allocations | Individual free |
GeneralPurposeAlloc |
Default (with safety) | Individual free |
ArenaAllocator |
Batch operations | Free all at once |
FixedBufferAllocator |
Stack-like, no heap | No free needed |
c_allocator |
C interop | Compatible with C |
Arrays and slices
Arrays
const arr = [5]i32{ 1, 2, 3, 4, 5 }; // Fixed size
const len = arr.len; // 5
// Initialize with repeated value
const zeros = [_]i32{0} ** 100; // 100 zeros
// Multi-dimensional
const matrix = [3][3]i32{
[_]i32{ 1, 2, 3 },
[_]i32{ 4, 5, 6 },
[_]i32{ 7, 8, 9 },
};
Slices
const arr = [_]i32{ 1, 2, 3, 4, 5 };
const slice: []const i32 = arr[1..4]; // [2, 3, 4]
// Slices are fat pointers: pointer + length
// Like std::span<T> in C++20
var dynamic = try allocator.alloc(i32, 10);
defer allocator.free(dynamic); // ⚠️ Must free!
String literals
const str: []const u8 = "Hello"; // String literal
const multiline =
\\Line 1
\\Line 2
\\Line 3
;
// ⚠️ Strings are just []const u8 (like C)
// No std::string, use ArrayList(u8)
Structs and enums
Structs
const Point = struct {
x: i32,
y: i32,
// Method (just a namespaced function)
pub fn distance(self: Point, other: Point) f32 {
const dx = @as(f32, @floatFromInt(other.x - self.x));
const dy = @as(f32, @floatFromInt(other.y - self.y));
return @sqrt(dx * dx + dy * dy);
}
};
const p1 = Point{ .x = 0, .y = 0 };
const p2 = Point{ .x = 3, .y = 4 };
const dist = p1.distance(p2); // 5.0
Enums
const Color = enum {
red,
green,
blue,
// Enum methods
pub fn isRed(self: Color) bool {
return self == .red;
}
};
const c: Color = .red; // Type inferred
Tagged unions
const Payload = union(enum) {
int: i32,
float: f64,
boolean: bool,
};
const p = Payload{ .int = 42 };
switch (p) {
.int => |value| std.debug.print("int: {}\n", .{value}),
.float => |value| std.debug.print("float: {}\n", .{value}),
.boolean => |value| std.debug.print("bool: {}\n", .{value}),
}
// ⚠️ switch must be exhaustive (no default needed if all covered)
Comptime
Compile-time execution
// Compute at compile time
const array_size = comptime fibonacci(10);
var array: [array_size]i32 = undefined;
fn fibonacci(n: u32) u32 {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// ⚠️ comptime guarantees compile-time evaluation
Generic data structures
fn List(comptime T: type) type {
return struct {
items: []T,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !List(T) {
return List(T){
.items = &[_]T{},
.allocator = allocator,
};
}
pub fn deinit(self: *List(T)) void {
self.allocator.free(self.items);
}
};
}
var int_list = try List(i32).init(allocator);
defer int_list.deinit();
Type reflection
const std = @import("std");
fn printType(comptime T: type) void {
const info = @typeInfo(T);
std.debug.print("Type: {}\n", .{@typeName(T)});
switch (info) {
.Int => |int_info| {
std.debug.print("Bits: {}\n", .{int_info.bits});
},
.Struct => |struct_info| {
std.debug.print("Fields: {}\n", .{struct_info.fields.len});
},
else => {},
}
}
Testing
Basic tests
const std = @import("std");
test "addition" {
try std.testing.expect(2 + 2 == 4);
}
test "allocator" {
const allocator = std.testing.allocator;
const memory = try allocator.alloc(u8, 100);
defer allocator.free(memory);
try std.testing.expect(memory.len == 100);
}
Run tests:
zig test file.zig # Run all tests
zig test file.zig --test-filter "addition" # Specific test
Test assertions
| Function | Purpose |
|---|---|
expect(condition) |
Assert boolean |
expectEqual(expected, actual) |
Compare values |
expectError(err, result) |
Expect specific error |
expectEqualStrings(a, b) |
Compare strings |
expectApproxEqAbs(a, b, tol) |
Float comparison |
Build system
build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "myapp",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
Build commands
zig build # Build project
zig build run # Build and run
zig build test # Run tests
zig build -Doptimize=ReleaseFast # Optimized build
Cross-compilation
# Build for different targets
zig build -Dtarget=x86_64-linux
zig build -Dtarget=aarch64-macos
zig build -Dtarget=x86_64-windows
# List all targets
zig targets
C interop
Import C headers
const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
});
pub fn main() void {
_ = c.printf("Hello from C!\n");
}
Export to C
export fn add(a: i32, b: i32) i32 {
return a + b;
}
// Generates C header with:
// int32_t add(int32_t a, int32_t b);
Build as C library:
zig build-lib math.zig -dynamic
# Generates libmath.so (Linux), libmath.dylib (macOS)
C types
| C Type | Zig Type |
|---|---|
int |
c_int |
long |
c_long |
char * |
[*c]u8 |
void * |
?*anyopaque |
size_t |
usize |
Control flow
If expressions
const x = if (condition) 5 else 10;
// Multi-statement blocks
const y = if (condition) blk: {
const temp = compute();
break :blk temp * 2;
} else 0;
While loops
var i: u32 = 0;
while (i < 10) : (i += 1) {
std.debug.print("{}\n", .{i});
}
// With optional continue expression
var sum: u32 = 0;
var i: u32 = 0;
while (i < 10) : (i += 1) {
sum += i;
}
For loops
const items = [_]i32{ 1, 2, 3, 4, 5 };
// Iterate over array/slice
for (items) |item| {
std.debug.print("{}\n", .{item});
}
// With index
for (items, 0..) |item, i| {
std.debug.print("[{}] = {}\n", .{i, item});
}
Switch expressions
const value: i32 = 2;
const result = switch (value) {
1 => "one",
2 => "two",
3, 4, 5 => "three to five",
else => "other",
};
// ⚠️ Switch must be exhaustive or have else
Pointers
Pointer types
// Single-item pointer
var x: i32 = 5;
const ptr: *i32 = &x;
ptr.* = 10; // Dereference
// Many-item pointer (like T*)
const arr = [_]i32{ 1, 2, 3 };
const ptr: [*]const i32 = &arr;
const first = ptr[0]; // Index like array
// C pointer (nullable, may have unknown length)
const c_ptr: [*c]i32 = null;
// ⚠️ *T is never null, use ?*T for nullable
Pointer sizes
const std = @import("std");
// Pointer to unknown-size array
var arr = [_]i32{ 1, 2, 3, 4, 5 };
const ptr: [*]i32 = &arr;
// Slice (fat pointer: pointer + length)
const slice: []i32 = arr[0..];
std.debug.print("Len: {}\n", .{slice.len});
// ⚠️ [*]T has no length, []T has length
Alignment
const aligned_ptr: *align(16) i32 = ptr;
// Get alignment
const alignment = @alignOf(i32); // Usually 4
// ⚠️ Over-aligned pointers require explicit casts
Gotchas
Integer overflow
var x: u8 = 255;
x += 1; // ⚠️ Runtime panic in Debug!
// Use wrapping arithmetic
x +%= 1; // Wraps to 0
x -%= 1; // Wraps to 255
x *%= 2; // Wrapping multiply
// Or saturating arithmetic
x +|= 1; // Saturates at 255
Array initialization
var arr: [100]i32 = undefined; // ⚠️ Uninitialized!
var zeros = [_]i32{0} ** 100; // ✓ All zeros
// Can't do: arr = {0}; like C99
String concatenation
// ⚠️ NO string concatenation operator
// const str = "Hello " ++ "World"; // Compile error
// Use ArrayList or format
var list = std.ArrayList(u8).init(allocator);
try list.appendSlice("Hello ");
try list.appendSlice("World");
Const pointers
const ptr: *i32 = &x; // ⚠️ Pointer is const, NOT pointee!
ptr.* = 42; // ✓ Allowed
const ptr: *const i32 = &x; // ✓ Pointee is const
// ptr.* = 42; // Compile error
Type inference limits
// ⚠️ Must specify allocator result type
const list = std.ArrayList(i32).init(allocator); // ✓
// const list = std.ArrayList.init(allocator); // Error: can't infer T
No function overloading
// ⚠️ Can't overload like C++
// fn add(a: i32, b: i32) i32 { ... }
// fn add(a: f64, b: f64) f64 { ... } // Error!
// Use generics or different names
fn addInt(a: i32, b: i32) i32 { ... }
fn addFloat(a: f64, b: f64) f64 { ... }
Defer order
var file1 = try openFile("a.txt");
defer file1.close(); // Runs second
var file2 = try openFile("b.txt");
defer file2.close(); // Runs first
// ⚠️ Defer runs in REVERSE order
Also see
- Zig Language Reference (ziglang.org)
- Zig Standard Library (ziglang.org)
- Zig Learn (ziglearn.org)
- Zig by Example (zig-by-example.com)
- Zig News (zig.news)