Skip to content

Zig Arrays and Slices

Arrays and slices are core data structures for handling sequential data in Zig. This chapter will cover their usage and best practices in detail.

Array Basics

Array Declaration and Initialization

zig
const std = @import("std");

pub fn main() void {
    // Array with explicit size
    const explicit_array: [5]i32 = [5]i32{ 1, 2, 3, 4, 5 };
    
    // Array with inferred size
    const inferred_array = [_]i32{ 10, 20, 30, 40, 50 };
    
    // Repeated initialization
    const repeated_array = [_]i32{42} ** 8;
    
    // Partial initialization (rest are zero values)
    const partial_array = [10]i32{ 1, 2, 3 };
    
    std.debug.print("Explicit array: ");
    for (explicit_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    std.debug.print("Inferred array: ");
    for (inferred_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    std.debug.print("Repeated array: ");
    for (repeated_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    std.debug.print("Partial initialization: ");
    for (partial_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
}

Array Properties and Operations

zig
const std = @import("std");

pub fn main() void {
    var numbers = [_]i32{ 1, 2, 3, 4, 5 };
    
    // Array length
    std.debug.print("Array length: {}\n", .{numbers.len});
    
    // Array element access
    std.debug.print("First element: {}\n", .{numbers[0]});
    std.debug.print("Last element: {}\n", .{numbers[numbers.len - 1]});
    
    // Modify array element
    numbers[2] = 100;
    std.debug.print("Modified array: ");
    for (numbers) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // Array comparison
    const array1 = [_]i32{ 1, 2, 3 };
    const array2 = [_]i32{ 1, 2, 3 };
    const array3 = [_]i32{ 1, 2, 4 };
    
    std.debug.print("array1 == array2: {}\n", .{std.mem.eql(i32, &array1, &array2)});
    std.debug.print("array1 == array3: {}\n", .{std.mem.eql(i32, &array1, &array3)});
}

Multidimensional Arrays

zig
const std = @import("std");

pub fn main() void {
    // 2D array
    const matrix = [3][3]i32{
        [3]i32{ 1, 2, 3 },
        [3]i32{ 4, 5, 6 },
        [3]i32{ 7, 8, 9 },
    };
    
    std.debug.print("3x3 Matrix:\n");
    for (matrix, 0..) |row, i| {
        std.debug.print("Row {}: ", .{i});
        for (row) |item| {
            std.debug.print("{:2} ", .{item});
        }
        std.debug.print("\n");
    }
    
    // 3D array
    const cube = [2][2][2]i32{
        [2][2]i32{
            [2]i32{ 1, 2 },
            [2]i32{ 3, 4 },
        },
        [2][2]i32{
            [2]i32{ 5, 6 },
            [2]i32{ 7, 8 },
        },
    };
    
    std.debug.print("\n2x2x2 Cube:\n");
    for (cube, 0..) |layer, i| {
        std.debug.print("Layer {}:\n", .{i});
        for (layer, 0..) |row, j| {
            std.debug.print("  Row {}: ", .{j});
            for (row) |item| {
                std.debug.print("{} ", .{item});
            }
            std.debug.print("\n");
        }
    }
}

Slice Basics

Slice Creation and Usage

zig
const std = @import("std");

pub fn main() void {
    var array = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    // Create slices
    const full_slice: []i32 = array[0..];           // Full slice
    const partial_slice: []i32 = array[2..7];       // Partial slice
    const end_slice: []i32 = array[5..];            // From index 5 to end
    const start_slice: []i32 = array[0..5];         // From start to index 4
    
    std.debug.print("Original array: ");
    for (array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    std.debug.print("Full slice: ");
    for (full_slice) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    std.debug.print("Partial slice [2..7]: ");
    for (partial_slice) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    std.debug.print("End slice [5..]: ");
    for (end_slice) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    std.debug.print("Start slice [0..5]: ");
    for (start_slice) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
}

Slice Properties and Operations

zig
const std = @import("std");

pub fn main() void {
    var data = [_]u8{ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
    var slice: []u8 = data[2..8];
    
    // Slice properties
    std.debug.print("Slice length: {}\n", .{slice.len});
    std.debug.print("Slice pointer: {*}\n", .{slice.ptr});
    
    // Slice content
    std.debug.print("Slice content: ");
    for (slice) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // Modify slice (affects original array)
    slice[0] = 99;
    slice[slice.len - 1] = 88;
    
    std.debug.print("Original array after modifying slice: ");
    for (data) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // Sub-slice
    const sub_slice = slice[1..4];
    std.debug.print("Sub-slice: ");
    for (sub_slice) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
}

Strings as Slices

String Slice Operations

zig
const std = @import("std");

pub fn main() void {
    const text = "Hello, Zig Programming!";
    
    // Strings are slices of type []const u8
    std.debug.print("Full string: {s}\n", .{text});
    std.debug.print("String length: {}\n", .{text.len});
    
    // String slicing
    const hello = text[0..5];
    const zig = text[7..10];
    const programming = text[11..22];
    
    std.debug.print("Hello: {s}\n", .{hello});
    std.debug.print("Zig: {s}\n", .{zig});
    std.debug.print("Programming: {s}\n", .{programming});
    
    // Character access
    std.debug.print("First character: {c} (ASCII: {})\n", .{ text[0], text[0] });
    std.debug.print("Last character: {c} (ASCII: {})\n", .{ text[text.len - 1], text[text.len - 1] });
    
    // Iterate through characters
    std.debug.print("Character by character: ");
    for (text) |char| {
        std.debug.print("{c}", .{char});
    }
    std.debug.print("\n");
}

String Processing Functions

zig
const std = @import("std");

// Find substring
fn findSubstring(haystack: []const u8, needle: []const u8) ?usize {
    if (needle.len > haystack.len) return null;
    
    for (haystack, 0..) |_, i| {
        if (i + needle.len > haystack.len) break;
        if (std.mem.eql(u8, haystack[i..i + needle.len], needle)) {
            return i;
        }
    }
    return null;
}

// Count character occurrences
fn countChar(text: []const u8, char: u8) usize {
    var count: usize = 0;
    for (text) |c| {
        if (c == char) count += 1;
    }
    return count;
}

// Reverse string
fn reverseString(allocator: std.mem.Allocator, text: []const u8) ![]u8 {
    var reversed = try allocator.alloc(u8, text.len);
    for (text, 0..) |char, i| {
        reversed[text.len - 1 - i] = char;
    }
    return reversed;
}

pub fn main() !void {
    const text = "Hello, Zig! Zig is great!";
    
    // Find substring
    if (findSubstring(text, "Zig")) |index| {
        std.debug.print("Found 'Zig' at position: {}\n", .{index});
    }
    
    // Count character occurrences
    const count = countChar(text, 'g');
    std.debug.print("Character 'g' appears {} times\n", .{count});
    
    // Reverse string
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const reversed = try reverseString(allocator, "Hello");
    defer allocator.free(reversed);
    
    std.debug.print("'Hello' reversed: {s}\n", .{reversed});
}

Memory Layout of Arrays and Slices

Memory Contiguity

zig
const std = @import("std");

pub fn main() void {
    var array = [_]i32{ 1, 2, 3, 4, 5 };
    
    std.debug.print("Array element addresses:\n");
    for (array, 0..) |*item, i| {
        std.debug.print("array[{}] = {} (address: {*})\n", .{ i, item.*, item });
    }
    
    // Verify memory contiguity
    const ptr0 = @intFromPtr(&array[0]);
    const ptr1 = @intFromPtr(&array[1]);
    const element_size = @sizeOf(i32);
    
    std.debug.print("\nMemory contiguity verification:\n");
    std.debug.print("Element size: {} bytes\n", .{element_size});
    std.debug.print("Address difference: {} bytes\n", .{ptr1 - ptr0});
    std.debug.print("Memory contiguous: {}\n", .{ptr1 - ptr0 == element_size});
    
    // Slice memory layout
    const slice: []i32 = array[1..4];
    std.debug.print("\nSlice information:\n");
    std.debug.print("Slice pointer: {*}\n", .{slice.ptr});
    std.debug.print("Slice length: {}\n", .{slice.len});
    std.debug.print("Slice size: {} bytes\n", .{@sizeOf(@TypeOf(slice))});
}

Dynamic Arrays (ArrayList)

ArrayList Basic Usage

zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // Create dynamic array
    var list = std.ArrayList(i32).init(allocator);
    defer list.deinit();
    
    // Add elements
    try list.append(10);
    try list.append(20);
    try list.append(30);
    
    std.debug.print("Initial list: ");
    for (list.items) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // Insert element
    try list.insert(1, 15);
    std.debug.print("After inserting 15: ");
    for (list.items) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // Remove element
    _ = list.orderedRemove(2);
    std.debug.print("After removing index 2: ");
    for (list.items) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // Batch append
    try list.appendSlice(&[_]i32{ 40, 50, 60 });
    std.debug.print("After batch append: ");
    for (list.items) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    std.debug.print("List length: {}\n", .{list.items.len});
    std.debug.print("List capacity: {}\n", .{list.capacity});
}

ArrayList Advanced Operations

zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    var numbers = std.ArrayList(i32).init(allocator);
    defer numbers.deinit();
    
    // Pre-allocate capacity
    try numbers.ensureTotalCapacity(100);
    std.debug.print("Pre-allocated capacity: {}\n", .{numbers.capacity});
    
    // Batch initialization
    for (0..10) |i| {
        try numbers.append(@intCast(i * 10));
    }
    
    // Find element
    const target = 50;
    var found_index: ?usize = null;
    for (numbers.items, 0..) |item, i| {
        if (item == target) {
            found_index = i;
            break;
        }
    }
    
    if (found_index) |index| {
        std.debug.print("Found {} at index {}\n", .{ target, index });
    }
    
    // Sort
    std.mem.sort(i32, numbers.items, {}, comptime std.sort.asc(i32));
    std.debug.print("After sorting: ");
    for (numbers.items) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // Reverse
    std.mem.reverse(i32, numbers.items);
    std.debug.print("After reversing: ");
    for (numbers.items) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // Clear but retain capacity
    numbers.clearRetainingCapacity();
    std.debug.print("After clearing - length: {}, capacity: {}\n", .{ numbers.items.len, numbers.capacity });
}

Array and Slice Algorithms

Search Algorithms

zig
const std = @import("std");

// Linear search
fn linearSearch(comptime T: type, array: []const T, target: T) ?usize {
    for (array, 0..) |item, i| {
        if (item == target) return i;
    }
    return null;
}

// Binary search (requires sorted array)
fn binarySearch(comptime T: type, array: []const T, target: T) ?usize {
    var left: usize = 0;
    var right: usize = array.len;
    
    while (left < right) {
        const mid = left + (right - left) / 2;
        if (array[mid] == target) {
            return mid;
        } else if (array[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return null;
}

pub fn main() void {
    const unsorted_array = [_]i32{ 64, 34, 25, 12, 22, 11, 90, 88, 76, 50, 42 };
    const sorted_array = [_]i32{ 11, 12, 22, 25, 34, 42, 50, 64, 76, 88, 90 };
    
    // Linear search
    const target1 = 22;
    if (linearSearch(i32, &unsorted_array, target1)) |index| {
        std.debug.print("Linear search: found {} at position {}\n", .{ target1, index });
    }
    
    // Binary search
    const target2 = 42;
    if (binarySearch(i32, &sorted_array, target2)) |index| {
        std.debug.print("Binary search: found {} at position {}\n", .{ target2, index });
    }
    
    // Search for non-existent element
    const target3 = 99;
    if (linearSearch(i32, &unsorted_array, target3)) |index| {
        std.debug.print("Found {} at position {}\n", .{ target3, index });
    } else {
        std.debug.print("Did not find {}\n", .{target3});
    }
}

Sorting Algorithms

zig
const std = @import("std");

// Bubble sort
fn bubbleSort(comptime T: type, array: []T) void {
    const n = array.len;
    for (0..n) |i| {
        for (0..n - i - 1) |j| {
            if (array[j] > array[j + 1]) {
                const temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

// Selection sort
fn selectionSort(comptime T: type, array: []T) void {
    const n = array.len;
    for (0..n) |i| {
        var min_idx = i;
        for (i + 1..n) |j| {
            if (array[j] < array[min_idx]) {
                min_idx = j;
            }
        }
        if (min_idx != i) {
            const temp = array[i];
            array[i] = array[min_idx];
            array[min_idx] = temp;
        }
    }
}

// Insertion sort
fn insertionSort(comptime T: type, array: []T) void {
    for (1..array.len) |i| {
        const key = array[i];
        var j: isize = @intCast(i - 1);
        
        while (j >= 0 and array[@intCast(j)] > key) {
            array[@intCast(j + 1)] = array[@intCast(j)];
            j -= 1;
        }
        array[@intCast(j + 1)] = key;
    }
}

pub fn main() void {
    // Test bubble sort
    var bubble_array = [_]i32{ 64, 34, 25, 12, 22, 11, 90 };
    std.debug.print("Before bubble sort: ");
    for (bubble_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    bubbleSort(i32, &bubble_array);
    std.debug.print("After bubble sort: ");
    for (bubble_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n\n");
    
    // Test selection sort
    var selection_array = [_]i32{ 64, 25, 12, 22, 11 };
    std.debug.print("Before selection sort: ");
    for (selection_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    selectionSort(i32, &selection_array);
    std.debug.print("After selection sort: ");
    for (selection_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n\n");
    
    // Test insertion sort
    var insertion_array = [_]i32{ 12, 11, 13, 5, 6 };
    std.debug.print("Before insertion sort: ");
    for (insertion_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    insertionSort(i32, &insertion_array);
    std.debug.print("After insertion sort: ");
    for (insertion_array) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
}

Best Practices for Arrays and Slices

1. Choose the Right Data Structure

zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // ✅ Fixed size, known data -> use array
    const fixed_data = [_]i32{ 1, 2, 3, 4, 5 };
    std.debug.print("Fixed array: {} elements\n", .{fixed_data.len});
    
    // ✅ Dynamic size, frequent modifications -> use ArrayList
    var dynamic_data = std.ArrayList(i32).init(allocator);
    defer dynamic_data.deinit();
    
    for (0..10) |i| {
        try dynamic_data.append(@intCast(i));
    }
    std.debug.print("Dynamic array: {} elements\n", .{dynamic_data.items.len});
    
    // ✅ Read-only access, partial data -> use slice
    const slice_data: []const i32 = dynamic_data.items[2..7];
    std.debug.print("Slice: {} elements\n", .{slice_data.len});
}

2. Bounds Checking and Safe Access

zig
const std = @import("std");

fn safeGet(comptime T: type, array: []const T, index: usize) ?T {
    if (index >= array.len) return null;
    return array[index];
}

fn safeSet(comptime T: type, array: []T, index: usize, value: T) bool {
    if (index >= array.len) return false;
    array[index] = value;
    return true;
}

pub fn main() void {
    var data = [_]i32{ 10, 20, 30, 40, 50 };
    
    // ✅ Safe access
    if (safeGet(i32, &data, 2)) |value| {
        std.debug.print("Safely got data[2] = {}\n", .{value});
    }
    
    if (safeGet(i32, &data, 10)) |value| {
        std.debug.print("Won't execute: {}\n", .{value});
    } else {
        std.debug.print("Index 10 out of bounds\n", .{});
    }
    
    // ✅ Safe set
    if (safeSet(i32, &data, 1, 99)) {
        std.debug.print("Successfully set data[1] = 99\n", .{});
    }
    
    if (!safeSet(i32, &data, 10, 99)) {
        std.debug.print("Failed to set data[10]: index out of bounds\n", .{});
    }
}

3. Memory Efficiency

zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // ✅ Pre-allocate capacity to avoid frequent reallocations
    var efficient_list = std.ArrayList(i32).init(allocator);
    defer efficient_list.deinit();
    
    try efficient_list.ensureTotalCapacity(1000); // Pre-allocate
    
    for (0..1000) |i| {
        efficient_list.appendAssumeCapacity(@intCast(i)); // No capacity check needed
    }
    
    std.debug.print("Efficient list: {} elements, capacity: {}\n", 
                    .{ efficient_list.items.len, efficient_list.capacity });
    
    // ✅ Use slices to avoid unnecessary copying
    const slice = efficient_list.items[100..200];
    std.debug.print("Slice processing: {} elements\n", .{slice.len});
}

Practical Application Example

Matrix Operations

zig
const std = @import("std");

const Matrix = struct {
    data: []f64,
    rows: usize,
    cols: usize,
    allocator: std.mem.Allocator,
    
    const Self = @This();
    
    pub fn init(allocator: std.mem.Allocator, rows: usize, cols: usize) !Self {
        const data = try allocator.alloc(f64, rows * cols);
        @memset(data, 0.0);
        
        return Self{
            .data = data,
            .rows = rows,
            .cols = cols,
            .allocator = allocator,
        };
    }
    
    pub fn deinit(self: *Self) void {
        self.allocator.free(self.data);
    }
    
    pub fn get(self: *const Self, row: usize, col: usize) f64 {
        return self.data[row * self.cols + col];
    }
    
    pub fn set(self: *Self, row: usize, col: usize, value: f64) void {
        self.data[row * self.cols + col] = value;
    }
    
    pub fn print(self: *const Self) void {
        for (0..self.rows) |i| {
            for (0..self.cols) |j| {
                std.debug.print("{d:6.2} ", .{self.get(i, j)});
            }
            std.debug.print("\n");
        }
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    var matrix = try Matrix.init(allocator, 3, 3);
    defer matrix.deinit();
    
    // Initialize matrix
    for (0..3) |i| {
        for (0..3) |j| {
            matrix.set(i, j, @floatFromInt(i * 3 + j + 1));
        }
    }
    
    std.debug.print("3x3 Matrix:\n");
    matrix.print();
}

Summary

This chapter covered Zig arrays and slices in detail:

  • ✅ Array declaration, initialization, and operations
  • ✅ Multidimensional array usage
  • ✅ Slice creation and properties
  • ✅ String handling as slices
  • ✅ Memory layout and contiguity
  • ✅ Dynamic arrays with ArrayList
  • ✅ Search and sorting algorithms
  • ✅ Best practices and practical applications

Arrays and slices are the most fundamental and important data structures in Zig. Mastering their usage is crucial for writing efficient Zig programs. In the next chapter, we'll learn about Zig structs and enums.

Content is for learning and research only.