Skip to content

Slicing Arrays

A slice is a view into an array.

A slice is a view into an array.

An array owns its storage. A slice refers to existing storage.

Given an array:

const a = [_]i32{ 10, 20, 30, 40, 50 };

A slice is created with the slicing operator:

const s = a[1..4];

The slice contains:

20 30 40

The lower bound is included. The upper bound is excluded.

So:

a[1..4]

means elements:

a[1]
a[2]
a[3]

The type of s is not an array type. It is a slice type:

[]const i32

This means:

a slice of read-only i32 values

The slice stores two things:

  1. A pointer to the first element.
  2. A length.

The slice does not copy the array.

This program demonstrates it:

const std = @import("std");

pub fn main() void {
    var a = [_]i32{ 10, 20, 30, 40, 50 };

    const s = a[1..4];

    a[2] = 999;

    std.debug.print("{d}\n", .{s[1]});
}

The output is:

999

The slice refers to the original array storage.

A slice has a .len field:

const std = @import("std");

pub fn main() void {
    const a = [_]u8{ 1, 2, 3, 4, 5 };

    const s = a[1..4];

    std.debug.print("{d}\n", .{s.len});
}

The output is:

3

Slices may be indexed like arrays:

const std = @import("std");

pub fn main() void {
    const a = [_]i32{ 100, 200, 300, 400 };

    const s = a[1..3];

    std.debug.print("{d}\n", .{s[0]});
    std.debug.print("{d}\n", .{s[1]});
}

The output is:

200
300

The index is relative to the slice, not the original array.

A slice may cover the whole array:

const s = a[0..a.len];

Since this is common, Zig provides shorthand:

const s = a[0..];

This means:

from index 0 to the end

Another example:

const s = a[2..];

This means:

from index 2 to the end

A slice may also be empty:

const s = a[2..2];

Its length is zero.

Slices are commonly passed to functions because the function does not need to know the array size at compile time.

This function prints any slice of integers:

const std = @import("std");

fn printSlice(s: []const i32) void {
    for (s) |x| {
        std.debug.print("{d}\n", .{x});
    }
}

pub fn main() void {
    const a = [_]i32{ 1, 2, 3, 4, 5 };

    printSlice(a[1..4]);
}

The output is:

2
3
4

The parameter type:

[]const i32

means:

a slice of read-only i32 values

The function accepts slices from arrays of any length.

Mutable slices are also possible:

const std = @import("std");

fn double(s: []i32) void {
    for (s) |*x| {
        x.* *= 2;
    }
}

pub fn main() void {
    var a = [_]i32{ 1, 2, 3, 4 };

    double(a[1..3]);

    for (a) |x| {
        std.debug.print("{d}\n", .{x});
    }
}

The output is:

1
4
6
4

The slice modified the original array.

Slices are one of the most important types in Zig. Much of the standard library uses them for strings, buffers, file data, and collections.

An array has its size in the type:

[5]u8

A slice does not:

[]u8

This difference is fundamental.

Arrays are fixed-size values. Slices are run-time views into memory.

Exercises.

Exercise 6-9. Create an array of 10 integers and make a slice containing the middle four elements.

Exercise 6-10. Write a function that computes the sum of a slice of integers.

Exercise 6-11. Write a function that sets every element of a mutable slice to zero.

Exercise 6-12. Create two slices referring to different parts of the same array and show that modifying the array affects both slices.