Slices
Slices can be thought of many-item pointers ([*]T
) with a length (usize
).
These use the syntax []T
. Slices are easier to use safely and more convinient
than many-item pointers, as they store the valid length of the buffer with them.
Slices are sometimes referred to as "fat pointers" as they're typically double
the size of a normal pointer. Slices are the most common way to pass around
buffers in Zig.
Coming from Go?▼
Slicing in Zig is similar to slicing in Go, but you replace array[start:end]
with array[start..end]
.
Moreover, in Go, there is no explicit ownership or memory management, meaning that slices point to memory owned by the garbage collector. However in Zig, slices point to manually-managed memory; slices are not tied to memory allocation. This has important implications:
- The validity and lifetime of the backing memory is in the hands of the programmer.
- Zig slices do not have a Cap field, as they do not resize.
For a resizeable/appendable buffer with ownership, have a look at ArrayList.
Unlike many-item pointers, for
loops work on slices.
The syntax x[n..m]
is used to create a slice from an array, an operation known
as slicing. This creates a "slice" - a view into the array consisting of a
pointer and a length. Slicing includes the first element (n
), but excludes the
last element (m
).
In this example, a const
slice is used in the total
function as it doesn't
write to the slice's buffer.
const expect = @import("std").testing.expect;
fn total(values: []const u8) usize {
var sum: usize = 0;
for (values) |v| sum += v;
return sum;
}
test "slices" {
const array = [_]u8{ 1, 2, 3, 4, 5 };
const slice = array[0..3];
try expect(total(slice) == 6);
}
When these n
and m
values are both known at compile time, slicing will
actually produce a pointer to an array. This is not an issue as a pointer to an
array i.e. *[N]T
will coerce to a slice - []T
.
const expect = @import("std").testing.expect;
test "slices 2" {
const array = [_]u8{ 1, 2, 3, 4, 5 };
const slice = array[0..3];
try expect(@TypeOf(slice) == *const [3]u8);
}
The syntax x[n..]
can also be used when you want to slice to the end.
test "slices 3" {
var array = [_]u8{ 1, 2, 3, 4, 5 };
var slice = array[0..];
_ = slice;
}
Let's again compare our pointer types.
Feature | *T | *[N]T | [*]T | []T |
---|---|---|---|---|
Dereferenceable | Yes, e.g. ptr.* | No | No | No |
Indexable | No | Yes | Yes | Yes |
Sliceable | No | Yes | Yes | Yes |
Element Count | Always 1 | Compile-time known | Unknown | Runtime known |
Arithmetic | No | No | Yes, e.g. ptr + 1 or ptr - 1 | No |
Size | @sizeOf(usize) | @sizeOf(usize) | @sizeOf(usize) | @sizeOf(usize) * 2 |
Attributes | Yes | Yes | Yes | Yes |
We can now apply our knowledge and make another program together.