Pointers & Slices
*T and []T say exactly what they point to and how far they reach. The slice — a pointer plus a length — is the workhorse of the standard library, and dereferencing is always written with .*.
Pointers
There is no reference syntax pretending to be a value. Creating a pointer needs &, reading through it needs .*, and both operations are visible in the source.
Single-Item Pointers (*T)
var value: u32 = 10;
const p: *u32 = &value; // address of value — a single-item pointer
p.* = 20; // write through the pointer
std.debug.print("{d}\n", .{value}); // 20 — mutation reached the original
Many-Item Pointers ([*]T)
[*]T points to an unknown sequence of items starting at an address — the C-like raw pointer. Arithmetic with ptr + n is allowed but unchecked; production Zig prefers slices precisely because they carry the length.
const Pointers
*const T allows reading, not writing. The compiler enforces it, so a function that only reads data can safely accept a const pointer and callers know nothing is mutated.
Slices
A slice is a fat pointer: a start pointer plus a length. Writing []const u8 for a string or []u32 for a buffer tells the reader both where and how many.
Basics
const arr = [_]u32{ 10, 20, 30, 40 };
const all: []const u32 = &arr; // slice over the whole array
const part: []const u32 = arr[1..3]; // items 1 and 2: length 2
std.debug.print("{d} {d}\n", .{ all.len, part.len }); // 4 2
Slices Are Fat Pointers
Figure 1 — a slice carries the address of its first item and the item count alongside it.
Slices exist to make out-of-bounds impossible at the type level: the runtime knows the length and checks every index in safe builds. Passing []const u8 around is Zig idioms standard way to share read-only data without ownership questions.
Slicing Operations
var buffer: [64]u8 = undefined;
const head: []u8 = buffer[0..16]; // first 16 bytes
const rest: []u8 = buffer[16..]; // from index 16 to the end
const exact: []u8 = buffer[10..12]; // two bytes, indices 10-11
Alignment
Every type has an alignment: the address multiple its data prefers (for example 4 for u32 on most CPUs). Misaligned access can crash or corrupt; Zig tracks alignment in the pointer type and checks casts.
Alignment Rules
const a = @alignOf(u32); // 4 on common targets
const s = @sizeOf(u32); // 4
// a pointer type records its alignment: *align(8) u32
@alignCast
When you have proven (or constructed) a stronger alignment than the type claims, @alignCast raises it. It is an explicit promise; the runtime verifies it in safety-checked builds.
Pointer Casts
Zig treats casting as a visible, checked operation. Two builtins dominate pointer work.
@ptrCast
const bytes: [*]const u8 = @ptrCast(&value);
// reinterpret the address of a u32 as a byte pointer — layout must match
Integers to Pointers and Back
Hardware and kernel code often work with raw addresses. Zig exposes the conversion explicitly: @intFromPtr(p) turns a pointer into a usize, and @ptrFromInt reverses it. Both are visible, and both demand discipline.
Safety
Unlike C, Zig keeps pointer mistakes diagnosable. Bounds checks, alignment checks, and the absence of implicit null pointers turn the classic C bug classes into loud, localized panics in safe builds.
Bounds Checking
const items = [_]u32{ 1, 2, 3 };
const bad = items[10]; // panic in Debug/ReleaseSafe: index out of bounds
// in ReleaseFast this becomes undefined behavior — keep safety in production
No Built-In Null Pointers
A plain *T cannot be null — the type forbids it. When absence must be representable, Zig uses optionals (?*T), which are covered in the optionals lesson. Nullability becomes a deliberate, type-level choice.
Common Pitfalls
Dangling After Free
A pointer to freed memory is still a valid pointer value — Zig cannot know it is stale. The discipline is lifetime pairing: allocate once, defer free, and never save the address beyond the defer.
Forgetting the Star
p and p.* are not interchangeable: forgetting .* is the most common beginner compile error, and the error message teaches the model — the pointer is the address, the star is the value it points to.
Next: Optionals & Null Safety — making absence explicit in the type.