Skip to main content

Arrays

Array Literals

Arrays in Zig have a fixed size known at compile-time:
const expect = @import("std").testing.expect;
const assert = @import("std").debug.assert;
const mem = @import("std").mem;

// array literal
const message = [_]u8{ 'h', 'e', 'l', 'l', 'o' };

// alternative initialization using result location
const alt_message: [5]u8 = .{ 'h', 'e', 'l', 'l', 'o' };

comptime {
    assert(mem.eql(u8, &message, &alt_message));
}

// get the size of an array
comptime {
    assert(message.len == 5);
}

// A string literal is a single-item pointer to an array.
const same_message = "hello";

comptime {
    assert(mem.eql(u8, &message, same_message));
}
String literals in Zig are arrays of u8 with a null terminator, making them [N:0]u8 types.

Array Iteration

test "iterate over an array" {
    var sum: usize = 0;
    for (message) |byte| {
        sum += byte;
    }
    try expect(sum == 'h' + 'e' + 'l' * 2 + 'o');
}

Modifying Arrays

var some_integers: [100]i32 = undefined;

test "modify an array" {
    for (&some_integers, 0..) |*item, i| {
        item.* = @intCast(i);
    }
    try expect(some_integers[10] == 10);
    try expect(some_integers[99] == 99);
}

Array Concatenation

Arrays can be concatenated at compile-time:
const part_one = [_]i32{ 1, 2, 3, 4 };
const part_two = [_]i32{ 5, 6, 7, 8 };
const all_of_it = part_one ++ part_two;

comptime {
    assert(mem.eql(i32, &all_of_it, &[_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }));
}

// remember that string literals are arrays
const hello = "hello";
const world = "world";
const hello_world = hello ++ " " ++ world;

comptime {
    assert(mem.eql(u8, hello_world, "hello world"));
}

Array Repetition

// ** does repeating patterns
const pattern = "ab" ** 3;
comptime {
    assert(mem.eql(u8, pattern, "ababab"));
}

// initialize an array to zero
const all_zero = [_]u16{0} ** 10;

comptime {
    assert(all_zero.len == 10);
    assert(all_zero[5] == 0);
}

Compile-Time Array Initialization

const Point = struct {
    x: i32,
    y: i32,
};

// use compile-time code to initialize an array
var fancy_array = init: {
    var initial_value: [10]Point = undefined;
    for (&initial_value, 0..) |*pt, i| {
        pt.* = Point{
            .x = @intCast(i),
            .y = @intCast(i * 2),
        };
    }
    break :init initial_value;
};

test "compile-time array initialization" {
    try expect(fancy_array[4].x == 4);
    try expect(fancy_array[4].y == 8);
}
const arr = [_]i32{ 1, 2, 3, 4, 5 };

Slices

Slices are runtime-known length pointers to arrays:
const expect = @import("std").testing.expect;
const expectEqualSlices = @import("std").testing.expectEqualSlices;

test "basic slices" {
    var array = [_]i32{ 1, 2, 3, 4 };
    var known_at_runtime_zero: usize = 0;
    _ = &known_at_runtime_zero;
    const slice = array[known_at_runtime_zero..array.len];

    // alternative initialization using result location
    const alt_slice: []const i32 = &.{ 1, 2, 3, 4 };

    try expectEqualSlices(i32, slice, alt_slice);

    try expect(@TypeOf(slice) == []i32);
    try expect(&slice[0] == &array[0]);
    try expect(slice.len == array.len);

    // If you slice with comptime-known start and end positions, the result is
    // a pointer to an array, rather than a slice.
    const array_ptr = array[0..array.len];
    try expect(@TypeOf(array_ptr) == *[array.len]i32);

    // You can perform a slice-by-length by slicing twice.
    var runtime_start: usize = 1;
    _ = &runtime_start;
    const length = 2;
    const array_ptr_len = array[runtime_start..][0..length];
    try expect(@TypeOf(array_ptr_len) == *[length]i32);

    // Using the address-of operator on a slice gives a single-item pointer.
    try expect(@TypeOf(&slice[0]) == *i32);
    // Using the `ptr` field gives a many-item pointer.
    try expect(@TypeOf(slice.ptr) == [*]i32);
    try expect(@intFromPtr(slice.ptr) == @intFromPtr(&slice[0]));
}
Slices have bounds checking. Accessing out-of-bounds indices will trigger a safety check failure in Debug and ReleaseSafe modes.

Empty Slices

test "empty slices" {
    const empty1 = &[0]u8{};
    // If the type is known you can use this short hand:
    const empty2: []u8 = &.{};
    try expect(empty1.len == 0);
    try expect(empty2.len == 0);
}

Pointers

Single-Item Pointers

Single-item pointers point to exactly one item:
test "single-item pointer" {
    var x: i32 = 10;
    const ptr: *i32 = &x;
    ptr.* = 20;
    try expect(x == 20);
}

Many-Item Pointers

Many-item pointers point to an unknown number of items:
test "many-item pointer" {
    const array = [_]u8{ 1, 2, 3, 4 };
    const ptr: [*]const u8 = &array;
    try expect(ptr[0] == 1);
    try expect(ptr[1] == 2);
}

Const Pointers

test "const pointer" {
    var x: i32 = 10;
    const ptr: *const i32 = &x;
    // ptr.* = 20; // Error: cannot assign to constant
    try expect(ptr.* == 10);
}

Compile-Time Pointers

const expect = @import("std").testing.expect;

test "comptime pointers" {
    comptime {
        var x: i32 = 1;
        const ptr = &x;
        ptr.* += 1;
        x += 1;
        try expect(ptr.* == 3);
    }
}
Pointers in Zig are checked for null in Debug and ReleaseSafe modes. Use optional pointers ?*T when null is a valid value.

Pointer Types Summary

*T       // pointer to exactly one item
*const T // const pointer to one item

Sentinel-Terminated Pointers

Pointers and arrays can have sentinel values:
test "sentinel-terminated" {
    // Null-terminated string
    const str: [*:0]const u8 = "hello";
    
    // Array with sentinel
    const arr: [5:0]u8 = "hello".*;
    try expect(arr[5] == 0);
}
Sentinel-terminated pointers are useful for C interop, where strings are null-terminated.

Pointer Arithmetic

Pointer arithmetic is possible with many-item pointers:
test "pointer arithmetic" {
    const array = [_]i32{ 1, 2, 3, 4, 5 };
    const ptr: [*]const i32 = &array;
    
    try expect(ptr[0] == 1);
    try expect((ptr + 2)[0] == 3);
}

Alignment

Pointers can specify alignment requirements:
test "pointer alignment" {
    var x: i32 align(16) = 10;
    const ptr: *align(16) i32 = &x;
    try expect(@intFromPtr(ptr) % 16 == 0);
}
Misaligned pointer access will cause a safety check failure in Debug and ReleaseSafe modes.

Build docs developers (and LLMs) love