3 Zig Syntax Essentials
- Types:
i32/u64(integers),f64(floats),bool,void - Pointers:
*T(single),[]T(slice),[*]T(many-item),?T(optional),!T(error union) - Composite:
struct,enum,union(tagged/untagged),packed struct(bit-level control) - Variables:
const(immutable),var(mutable). Preferconst. - Control flow:
if/while/for/switch(switch must be exhaustive) - Functions: Return types required. Use
comptimefor generics. - Builtins:
@intCast,@TypeOf,@sizeOffor type operations - Jump to: Types §2.1 | Pointers §2.2 | Composite §2.3 | Control Flow §2.5
Zig’s syntax prioritizes explicitness and compile-time verification. This chapter covers essential syntax needed before exploring idioms in Chapter 3. Skip if already familiar with Zig syntax.
3.1 Types and Declarations
Integers are sized explicitly. No implicit conversions.
Common integer types: i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize
Arbitrary bit-width integers enable precise memory control:
Use cases: Protocol implementations, bit-field structures, hardware interfaces.
Floats follow IEEE 754:
Strings are UTF-8 byte slices:
Type aliases use const:
3.2 Pointers, Arrays, and Slices
Pointers require explicit types. No automatic dereferencing.
| Type | Description | Use Case |
|---|---|---|
*T |
Single-item pointer | Passing by reference |
[]T |
Slice (pointer + length) | Working with sequences |
[*]T |
Many-item pointer | C interop, manual indexing |
[*:0]T |
Sentinel-terminated | Null-terminated C strings |
Arrays have compile-time fixed size:
Slices are runtime-sized views:
Common pitfall: Array decay to slice requires explicit syntax:
3.3 Composite Types
Structs
Structs group related data with named fields:
const Point = struct {
x: i32,
y: i32,
// Methods are just namespaced functions
pub fn distance(self: Point, other: Point) f64 {
const dx = @as(f64, @floatFromInt(self.x - other.x));
const dy = @as(f64, @floatFromInt(self.y - other.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.0Anonymous structs enable lightweight data grouping:
Default field values:
Enums
Enums define named constants with optional associated values:
Tagged unions combine enums with data:
const Value = union(enum) {
int: i64,
float: f64,
string: []const u8,
boolean: bool,
};
const v1 = Value{ .int = 42 };
const v2 = Value{ .string = "hello" };
// Switch handles all cases
switch (v1) {
.int => |val| std.debug.print("int: {}\n", .{val}),
.float => |val| std.debug.print("float: {}\n", .{val}),
.string => |s| std.debug.print("string: {s}\n", .{s}),
.boolean => |b| std.debug.print("bool: {}\n", .{b}),
}Enum with explicit values:
Unions
Untagged unions save memory by overlapping fields:
Tagged unions are safer - require enum tag:
Packed Structs
Packed structs control bit-level layout:
Use cases: - Hardware register interfaces - Network protocol headers - File format parsing - Bit-field manipulation
Packed struct with bit-width integers:
Memory guarantees: - Packed structs have no padding between fields - Total size equals sum of field bit widths (rounded to byte boundary) - Field order matches declaration order
3.4 Optionals and Error Unions
Optionals represent potentially absent values using ?T:
Error unions represent failable operations using !T:
| Feature | Optional ?T |
Error Union !T |
|---|---|---|
| Represents | Absence | Failure |
| Null-like | null |
error.Name |
| Unwrap | orelse |
catch |
| Propagate | orelse return |
try |
See Ch7 for comprehensive error handling patterns.
3.5 Variables and Constants
Immutability is default:
Type inference reduces boilerplate:
Shadowing is not allowed in same scope:
3.6 Control Flow
If expressions return values:
While loops with optional continue expression:
For loops iterate over arrays and slices:
Switch must be exhaustive:
Common pitfall: defer in loops executes per iteration:
// ❌ Allocates 100 buffers, frees all at end
for (0..100) |_| {
const buf = try allocator.alloc(u8, 1024);
defer allocator.free(buf); // Defers until loop end
// use buf
}
// ✅ Use nested block for immediate cleanup
for (0..100) |_| {
{
const buf = try allocator.alloc(u8, 1024);
defer allocator.free(buf); // Defers until block end
// use buf
} // buf freed here
}See Ch3 §3.3 for defer and errdefer patterns.
3.7 Functions
Return type required (except void):
Generic functions use comptime parameters:
Error return types integrate with try:
3.8 Builtin Functions
Type conversion requires explicit builtins:
Type introspection at compile time:
Common builtins:
| Builtin | Purpose | Example |
|---|---|---|
@intCast |
Safe integer conversion | @intCast(value) |
@floatFromInt |
Integer to float | @floatFromInt(42) |
@intFromFloat |
Float to int (truncate) | @intFromFloat(3.14) |
@intFromEnum |
Enum to integer | @intFromEnum(Status.ok) |
@TypeOf |
Get type of expression | @TypeOf(x) |
@sizeOf |
Size in bytes | @sizeOf(T) |
@bitCast |
Reinterpret bits | @bitCast(value) |
@import |
Load module | @import("std") |
@compileError |
Emit compile error | @compileError("msg") |
Full builtin reference: https://ziglang.org/documentation/master/#Builtin-Functions
3.9 Syntax Quick Reference
// Primitive types
const int: i32 = -42;
const uint: u64 = 100;
const float: f64 = 3.14;
const boolean: bool = true;
const bit_width: u7 = 127; // Arbitrary bit-width
const optional: ?i32 = null;
const err_union: !i32 = error.Fail;
// Composite types
const Point = struct { x: i32, y: i32 };
const Color = enum { red, green, blue };
const Value = union(enum) { int: i64, string: []u8 };
const Flags = packed struct { read: bool, write: bool };
// Variables
const immutable = 42;
var mutable: i32 = 10;
// Pointers & Slices
const ptr: *const i32 = ∫
const slice: []const u8 = "string";
const array: [3]i32 = .{ 1, 2, 3 };
// Control flow
if (condition) value1 else value2
while (cond) : (continue_expr) { }
for (items) |item| { }
for (items, 0..) |item, idx| { }
switch (value) {
case => result,
else => default,
}
// Functions
fn name(param: Type) ReturnType { }
fn generic(comptime T: type, val: T) T { }
fn fallible() !T { }
// Common builtins
@intCast(value)
@TypeOf(expr)
@sizeOf(T)
@bitCast(value)
@import("module")Next: Chapter 3 covers idiomatic patterns: defer, error handling, comptime generics, and naming conventions.