⚡ Zig Guide LiveUnofficial
✓ Zig 0.17.0-dev.1454+5faa79730On an older Zig?

Pointers

A *T refers to exactly one T. Take an address with &, and dereference with .*, a postfix operator, so it chains left to right like field access.

const std = @import("std");
const expect = std.testing.expect;

fn increment(num: *u8) void {
    num.* += 1;
}

test "take an address and dereference" {
    var x: u8 = 1;
    increment(&x);
    try expect(x == 2);
}

test "pointers to const are const" {
    const x: u8 = 1;
    // `&x` here is a `*const u8`; assigning through it would not compile.
    const ptr: *const u8 = &x;
    try expect(ptr.* == 1);
}

test "pointers are never null" {
    // There is no null `*T`. Absence is spelled `?*T`, and costs nothing
    // extra because the null address is used as the tag.
    var x: u8 = 5;
    const ptr: *u8 = &x;
    const maybe: ?*u8 = ptr;
    try expect(maybe != null);
    try expect(@sizeOf(?*u8) == @sizeOf(*u8));
}

Never null

There is no null *T. If a pointer may be absent, its type says so: ?*T. This is not merely a convention: the compiler will not let you dereference the optional without unwrapping it first.

The representation costs nothing. Zig uses the null address as the null tag, so ?*T is the same size as *T. You get the safety without the wrapper.

Constness travels with the pointer

&some_const gives you a *const T, and you cannot assign through it. Note that this is about what the pointer permits, which is separate from whether the pointer variable itself can be reassigned:

var p: *const u8 = &a;   // p can point elsewhere; p.* cannot be written
const q: *u8 = &b;       // q always points at b; q.* can be written