A variable has a value. In Zig, a variable may also have an address.
The address of a value is obtained with &.
const std = @import("std");
pub fn main() void {
var x: i32 = 10;
const p = &x;
std.debug.print("{d}\n", .{p.*});
}The expression &x means “the address of x.” The value stored in p is a pointer to x.
The expression p.* means “the value pointed to by p.” This is called dereferencing the pointer.
The program prints:
10A pointer type is written with *.
*i32This means “pointer to i32.”
The type of p in the previous program is inferred by the compiler. We could write it explicitly:
var x: i32 = 10;
const p: *i32 = &x;A pointer does not contain an i32. It contains the address of an i32.
Changing through a pointer changes the original value.
const std = @import("std");
pub fn main() void {
var x: i32 = 10;
const p: *i32 = &x;
p.* = 20;
std.debug.print("{d}\n", .{x});
}This prints:
20There is only one integer here, x. The pointer p gives another way to reach it.
Pointers follow the same mutability rule as other Zig values. A const pointer binding means the pointer variable cannot be changed to point somewhere else. It does not by itself make the pointed-to value constant.
var x: i32 = 10;
const p = &x;
p.* = 20; // okHere p is constant, but x is mutable.
A pointer to constant data has a different type:
const y: i32 = 10;
const q: *const i32 = &y;The type *const i32 means “pointer to constant i32.” Through this pointer, the value cannot be changed.
q.* = 20; // errorThis distinction is important.
var x: i32 = 10;
const p: *i32 = &x; // pointer to mutable i32
const q: *const i32 = &x; // pointer to constant view of i32Both pointers may refer to the same storage. But q does not permit writing through it.
Zig has no implicit pointer dereference for ordinary values. If p is a pointer, p.* is the value.
var n: i32 = 7;
const p = &n;
std.debug.print("{d}\n", .{p.*});This explicitness is deliberate. It keeps address operations visible.
A pointer may be passed to a function.
const std = @import("std");
fn increment(p: *i32) void {
p.* += 1;
}
pub fn main() void {
var count: i32 = 0;
increment(&count);
increment(&count);
std.debug.print("{d}\n", .{count});
}This prints:
2The function receives a pointer to count. It does not receive a copy of the integer. It receives a way to modify the original integer.
Pointers are common when a function must modify a value, avoid copying a large value, or refer to storage whose identity matters.
Zig also permits pointers to structs.
const std = @import("std");
const Point = struct {
x: i32,
y: i32,
};
fn moveRight(p: *Point) void {
p.x += 1;
}
pub fn main() void {
var point = Point{ .x = 3, .y = 4 };
moveRight(&point);
std.debug.print("({d}, {d})\n", .{ point.x, point.y });
}This prints:
(4, 4)Notice the field access in moveRight:
p.x += 1;For pointers to structs, Zig allows field access through the pointer. This is equivalent in meaning to accessing the field of the pointed-to struct.
For plain values, dereferencing is explicit:
p.*For struct fields, the convenient form is used:
p.xA pointer must point to valid storage. It is an error to keep a pointer to a local variable after that variable is gone.
fn bad() *i32 {
var x: i32 = 10;
return &x; // invalid idea
}The variable x belongs to the function call. When the function returns, x no longer exists. A pointer to it would be useless and dangerous.
Zig makes many such errors visible. But pointers still require care. They are addresses. They depend on the lifetime, alignment, and mutability of the storage they refer to.
The simplest rule is this: use a pointer only while the pointed-to value is still alive, and write through it only when the pointed-to value is mutable.
Exercise 5-1. Write a function zero that takes *i32 and sets the pointed-to value to zero.
Exercise 5-2. Write a function swap that takes two *i32 values and exchanges the integers they point to.
Exercise 5-3. Declare a const i32, take its address, and try to modify it through the pointer. Read the compiler error.
Exercise 5-4. Define a Rectangle struct with width and height, then write a function that doubles both fields through a pointer.