Many-item Pointers
const std = @import("std");
const expect = std.testing.expect;
test "many-item pointers support indexing" {
var array = [_]u8{ 1, 2, 3, 4 };
const many: [*]u8 = &array;
// Indexing and pointer arithmetic are allowed, but there is no `.len`
// and therefore no bounds checking. You are responsible for the range.
try expect(many[0] == 1);
try expect(many[3] == 4);
}
test "convert to a slice by supplying the length" {
var array = [_]u8{ 1, 2, 3, 4 };
const many: [*]u8 = &array;
// The length is the piece of information `[*]T` is missing; adding it
// back yields a `[]T`, which is bounds-checked again.
const slice: []u8 = many[0..3];
try expect(slice.len == 3);
}
test "single-item pointers do not index" {
// A `*T` is one item. `ptr[1]` is a compile error, which is the whole
// reason Zig distinguishes `*T` from `[*]T` where C has only `T*`.
var x: u8 = 9;
const single: *u8 = &x;
try expect(single.* == 9);
}C has one pointer type, T*, which is used for three different things: one
item, many items, and “maybe nothing”. Zig splits them apart:
| Zig | Meaning |
|---|---|
*T | exactly one T |
[*]T | many T, count unknown |
[]T | many T, count known (a slice) |
?*T | one T, or nothing |
What [*]T gives up
A many-item pointer supports indexing and pointer arithmetic, but has no .len
and therefore no bounds checking. It exists mainly for C interop and for
implementing the safe abstractions on top.
The moment you know the length, convert to a slice:
const slice: []u8 = many[0..count];
That single step buys back bounds checks, for iteration, and .len. Prefer
slices everywhere except the boundary where a length genuinely is not available.
Correspondingly, *T deliberately does not support indexing: ptr[1] on a
single-item pointer is a compile error, catching a class of C bug at the type
level.