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