Zig Arrays

An array is a fixed-size collection of values of the same type stored in a continuous block of memory. The size of an array is set at compile time and never changes. When you need a list of numbers, strings, or any values where the count is known in advance, arrays are the right tool.

Declaring an Array

const temps: [5]f32 = [5]f32{ 22.5, 19.0, 25.3, 18.7, 23.1 };

The type [5]f32 means: an array of exactly 5 elements, each a 32-bit float. You can let Zig count the elements using the _ size infer:

const temps = [_]f32{ 22.5, 19.0, 25.3, 18.7, 23.1 };
//            ^^^
//     Zig counts = 5
  Memory layout:
  Index:   [0]    [1]    [2]    [3]    [4]
  Value: [22.5] [19.0] [25.3] [18.7] [23.1]
         |------ 5 × 4 bytes = 20 bytes total -------|

Accessing Elements

const colors = [_][]const u8{ "Red", "Green", "Blue" };

std.debug.print("First:  {s}\n", .{colors[0]});
std.debug.print("Second: {s}\n", .{colors[1]});
std.debug.print("Third:  {s}\n", .{colors[2]});
  colors:
  Index:  0       1       2
       ["Red"] ["Green"] ["Blue"]
          ↑
       colors[0] = "Red"

Arrays in Zig start at index 0. The last valid index is always length - 1. Accessing an index outside this range causes a runtime panic in debug builds — Zig checks bounds automatically.

Modifying Array Elements

Arrays declared with var can be modified. Arrays declared with const cannot:

var scores = [_]u32{ 80, 90, 70 };
scores[1] = 95;  // Change second element from 90 to 95
std.debug.print("{d}\n", .{scores[1]});  // 95

Array Length

const primes = [_]u32{ 2, 3, 5, 7, 11, 13 };
const count = primes.len;
std.debug.print("Number of primes: {d}\n", .{count});  // 6

The .len property gives the number of elements. Because the array size is fixed at compile time, .len is a compile-time constant — the compiler knows it without running the program.

Iterating Over an Array

const days = [_][]const u8{
    "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
};

for (days, 0..) |day, i| {
    std.debug.print("Day {d}: {s}\n", .{i + 1, day});
}
  i=0, day="Mon" → "Day 1: Mon"
  i=1, day="Tue" → "Day 2: Tue"
  ...
  i=6, day="Sun" → "Day 7: Sun"

Multi-Dimensional Arrays

An array of arrays creates a grid structure — useful for tables, matrices, and game boards:

const grid = [3][3]u8{
    [_]u8{ 1, 2, 3 },
    [_]u8{ 4, 5, 6 },
    [_]u8{ 7, 8, 9 },
};
  Visual grid:
  [0][0]=1  [0][1]=2  [0][2]=3
  [1][0]=4  [1][1]=5  [1][2]=6
  [2][0]=7  [2][1]=8  [2][2]=9

  Access center element:
  grid[1][1] = 5
for (grid) |row| {
    for (row) |cell| {
        std.debug.print("{d} ", .{cell});
    }
    std.debug.print("\n", .{});
}

Output:

1 2 3
4 5 6
7 8 9

Array Initialization Patterns

All Elements the Same Value

// Initialize all 10 elements to zero
const zeroes = [_]u32{0} ** 10;

// Initialize all 5 elements to 255
const maxed = [_]u8{255} ** 5;
  zeroes: [ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 ]
  maxed:  [255|255|255|255|255]

The ** operator repeats the element the specified number of times. This is a compile-time operation — no runtime loop needed.

Undefined — Reserve Space Without Initializing

var buffer: [100]u8 = undefined;
// Fill later...
buffer[0] = 'H';
buffer[1] = 'i';

Concatenating Arrays at Compile Time

const a = [_]u32{ 1, 2, 3 };
const b = [_]u32{ 4, 5, 6 };
const combined = a ++ b;
// combined = [1, 2, 3, 4, 5, 6]

The ++ operator joins two arrays into a new one at compile time. Both arrays must contain elements of the same type. The result is a new array with a length equal to the sum of both.

Arrays vs Slices

  Array:                         Slice:
  +--------------------------+   +---------------------------+
  | Fixed size known at      |   | Flexible — points to part |
  | compile time             |   | of an array               |
  | [5]u32                   |   | []u32                     |
  | Owns its memory          |   | Borrows memory            |
  | Cannot resize            |   | Length stored separately  |
  +--------------------------+   +---------------------------+

  const arr: [5]u32 = .{1,2,3,4,5};
  const slc: []const u32 = arr[1..4];  // elements 1, 2, 3

Passing Arrays to Functions

fn total(data: []const u32) u32 {
    var sum: u32 = 0;
    for (data) |val| sum += val;
    return sum;
}

const nums = [_]u32{ 10, 20, 30, 40 };
const t = total(&nums);
std.debug.print("Total: {d}\n", .{t});  // 100

Passing &nums (the address of the array) converts it to a slice automatically in this context. Functions that accept []const u32 work with arrays of any length — much more flexible than typing the exact size into the function signature.

Practical Example: Class Grade Average

const std = @import("std");

pub fn main() void {
    const grades = [_]f32{ 88.0, 92.5, 75.0, 96.0, 83.5, 79.0 };
    var total: f32 = 0;

    for (grades) |g| total += g;

    const avg = total / @as(f32, @floatFromInt(grades.len));
    std.debug.print("Class average: {d:.2}\n", .{avg});
}

Output:

Class average: 85.67

Leave a Comment

Your email address will not be published. Required fields are marked *