A many-item pointer points to the first item in a sequence.
Its type is written with [*].
var items = [_]i32{ 10, 20, 30 };
const p: [*]i32 = &items;The type [*]i32 means “pointer to many i32 values.” It does not store a length. It says where the first item is, but not how many items may be used.
Items are accessed with an index.
const first = p[0];
const second = p[1];This program prints the first three values.
const std = @import("std");
pub fn main() void {
var items = [_]i32{ 10, 20, 30 };
const p: [*]i32 = &items;
std.debug.print("{d} {d} {d}\n", .{ p[0], p[1], p[2] });
}The output is:
10 20 30A many-item pointer is close to a C pointer. It may be indexed. It may be moved with pointer arithmetic. But because it has no length, it must be used carefully.
const q = p + 1;
std.debug.print("{d}\n", .{q[0]});Here q points at the second element of the same array. q[0] is the same value as p[1].
Many-item pointers are useful when working with C APIs, raw memory, or sentinel-terminated data. They are less common in ordinary Zig code. Most Zig code uses slices instead.
A slice has a pointer and a length.
var items = [_]i32{ 10, 20, 30 };
const s: []i32 = items[0..];The slice s knows that it contains three elements.
std.debug.print("{d}\n", .{s.len});A many-item pointer does not know this.
const p: [*]i32 = &items;
// p has no len fieldThis is the main difference.
Use a many-item pointer when the length is known by some other rule. Use a slice when the length should travel with the pointer.
A function that takes a many-item pointer must get the length separately if it needs bounds.
fn sum(ptr: [*]const i32, len: usize) i32 {
var total: i32 = 0;
var i: usize = 0;
while (i < len) : (i += 1) {
total += ptr[i];
}
return total;
}The caller must pass both values.
const result = sum(&items, items.len);This is a common C shape: pointer plus length.
In Zig, the same function is usually clearer with a slice.
fn sum(items: []const i32) i32 {
var total: i32 = 0;
for (items) |item| {
total += item;
}
return total;
}The slice version carries the length in the argument itself.
Many-item pointers may point to mutable or constant data.
const p: [*]i32 = &items; // may write items
const q: [*]const i32 = &items; // may only read itemsWriting through a mutable many-item pointer changes the original storage.
p[0] = 99;Reading through a constant many-item pointer is allowed.
const x = q[0];Writing through it is not.
q[0] = 99; // errorMany-item pointers can also be sentinel-terminated.
const name: [*:0]const u8 = "zig";The type [*:0]const u8 means a many-item pointer to bytes, ending at a zero byte. This is the shape used by many C strings.
The sentinel is not a length. It is a value that marks the end. Code must scan until it finds the sentinel.
For normal Zig strings, use slices.
const name: []const u8 = "zig";For C strings, sentinel pointers are often needed.
const c_name: [*:0]const u8 = "zig";A many-item pointer is a low-level tool. It is powerful because it contains little information. It is dangerous for the same reason.
The rule is simple: if the number of elements matters, prefer a slice. Use a many-item pointer only when the size is known elsewhere, or when an external interface requires it.
Exercise 5-9. Write a function first that takes [*]const i32 and returns the first item.
Exercise 5-10. Write a function sumMany that takes [*]const i32 and a length.
Exercise 5-11. Rewrite sumMany to take []const i32.
Exercise 5-12. Create a many-item pointer to an array and modify the second element through it.