A slice is a pointer and a length.
var items = [_]i32{ 10, 20, 30 };
const s: []i32 = items[0..];The type []i32 means “slice of mutable i32 values.” The slice does not own the array. It only refers to part of it.
The length is stored in the slice.
const std = @import("std");
pub fn main() void {
var items = [_]i32{ 10, 20, 30 };
const s: []i32 = items[0..];
std.debug.print("{d}\n", .{s.len});
}This prints:
3The elements are indexed in the usual way.
std.debug.print("{d}\n", .{s[0]});A slice checks its bounds when safety checks are enabled. If the slice has length 3, valid indexes are 0, 1, and 2.
const x = s[3]; // out of boundsThis is an error in safe builds.
A slice can refer to the whole array.
const all = items[0..];It can also refer to part of the array.
const middle = items[1..3];The slice middle contains the elements at indexes 1 and 2. The end index is not included.
items[1..3] // items[1], items[2]Changing a mutable slice changes the original array.
const std = @import("std");
pub fn main() void {
var items = [_]i32{ 10, 20, 30 };
const s = items[0..];
s[1] = 99;
std.debug.print("{d}\n", .{items[1]});
}This prints:
99There is still only one array. The slice is another view of its storage.
A slice may be constant.
const s: []const i32 = items[0..];The type []const i32 means that the elements may be read through the slice, but not written through it.
const x = s[0]; // ok
s[0] = 1; // errorUse []const T for function parameters when the function only reads the elements.
fn sum(items: []const i32) i32 {
var total: i32 = 0;
for (items) |item| {
total += item;
}
return total;
}Use []T when the function may change the elements.
fn zero(items: []i32) void {
for (items) |*item| {
item.* = 0;
}
}In this loop, |*item| captures a pointer to each element. That permits assignment to the element itself.
Without the *, the loop value is a copy.
for (items) |item| {
// item is a value
}With the *, it is a pointer to the element.
for (items) |*item| {
item.* = 0;
}Slices are the normal way to pass arrays to functions in Zig. They carry their own length, so the function does not need a separate length argument.
const std = @import("std");
fn printAll(items: []const i32) void {
for (items) |item| {
std.debug.print("{d}\n", .{item});
}
}
pub fn main() void {
const a = [_]i32{ 1, 2, 3 };
printAll(a[0..]);
}A string literal is a slice of constant bytes.
const name: []const u8 = "zig";Its length is the number of bytes.
std.debug.print("{d}\n", .{name.len});For "zig", the length is 3.
A slice may be empty.
const empty = items[0..0];An empty slice has length zero. It still has a pointer value, but no element may be indexed.
empty.len // 0Slices are small values. Passing a slice copies the pointer and the length. It does not copy the elements.
This is why slices are used so often: they are cheap to pass, explicit about length, and clear about mutability.
The common forms are:
[]T // mutable slice
[]const T // constant sliceUse a slice when a function needs many adjacent values and the number of values matters.
Exercise 5-13. Write sum for []const i32.
Exercise 5-14. Write fill that takes []u8 and a byte, and assigns that byte to every element.
Exercise 5-15. Write max that takes []const i32 and returns the largest value.
Exercise 5-16. Create an array of five integers, take a slice of the middle three, and modify them through the slice.