An array is a sequence of values of the same type.
A fixed-size array has its length as part of its type. This array has five u8 values:
const a = [5]u8{ 10, 20, 30, 40, 50 };The type of a is:
[5]u8This means: an array of 5 values, each of type u8.
Array elements are numbered from zero.
const std = @import("std");
pub fn main() void {
const a = [5]u8{ 10, 20, 30, 40, 50 };
std.debug.print("{d}\n", .{a[0]});
std.debug.print("{d}\n", .{a[4]});
}The output is:
10
50The expression a[0] selects the first element. The expression a[4] selects the last element. Since the array has length 5, there is no a[5].
Zig checks array indexing when it can. An index known to be outside the array is an error. An index computed at run time is checked in safe builds.
An array may be mutable:
const std = @import("std");
pub fn main() void {
var a = [3]i32{ 1, 2, 3 };
a[0] = 10;
a[2] = 30;
std.debug.print("{d} {d} {d}\n", .{ a[0], a[1], a[2] });
}The output is:
10 2 30The length of an array is available with .len:
const std = @import("std");
pub fn main() void {
const a = [4]i32{ 7, 8, 9, 10 };
std.debug.print("length = {d}\n", .{a.len});
}The output is:
length = 4The length is not stored as a separate run-time field in the ordinary sense. It is part of the array type. The compiler knows it.
A common loop over an array is:
const std = @import("std");
pub fn main() void {
const a = [5]i32{ 1, 2, 3, 4, 5 };
for (a) |x| {
std.debug.print("{d}\n", .{x});
}
}Here x is each element of the array.
To get both the index and the element, write:
const std = @import("std");
pub fn main() void {
const a = [3]i32{ 10, 20, 30 };
for (a, 0..) |x, i| {
std.debug.print("a[{d}] = {d}\n", .{ i, x });
}
}The output is:
a[0] = 10
a[1] = 20
a[2] = 30Arrays are values. Assigning an array copies the whole array.
const std = @import("std");
pub fn main() void {
var a = [3]i32{ 1, 2, 3 };
var b = a;
b[0] = 99;
std.debug.print("a[0] = {d}\n", .{a[0]});
std.debug.print("b[0] = {d}\n", .{b[0]});
}The output is:
a[0] = 1
b[0] = 99Changing b does not change a. They are separate arrays.
This matters. A large array passed by value is copied. When a function should work on the original array, pass a pointer or a slice.
const std = @import("std");
fn setFirst(a: *[3]i32) void {
a[0] = 100;
}
pub fn main() void {
var nums = [3]i32{ 1, 2, 3 };
setFirst(&nums);
std.debug.print("{d}\n", .{nums[0]});
}The output is:
100The type *[3]i32 means a pointer to an array of three i32 values. The expression &nums gives the address of the array.
Fixed-size arrays are best when the length is known at compile time and belongs to the data itself: a color with four channels, a small buffer, a machine word split into bytes, a table with a fixed number of entries.
For data whose length changes at run time, Zig usually uses slices. A slice is a pointer plus a length. Arrays and slices are closely related, but they are not the same type.
Exercises.
Exercise 6-1. Write a program that declares an array of 10 integers and prints them in order.
Exercise 6-2. Modify the program to print the elements in reverse order.
Exercise 6-3. Write a function that takes *[5]i32 and sets every element to zero.
Exercise 6-4. Write a program that copies one array to another, changes the second array, and prints both arrays.