Slices

Slices

test.zig

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

test "basic slices" {
    var array = [_]i32{ 1, 2, 3, 4 };
    // A slice is a pointer and a length. The difference between an array and
    // a slice is that the array's length is part of the type and known at
    // compile-time, whereas the slice's length is known at runtime.
    // Both can be accessed with the `len` field.
    var known_at_runtime_zero: usize = 0;
    const slice = array[known_at_runtime_zero..array.len];
    try expect(&slice[0] == &array[0]);
    try expect(slice.len == array.len);

    // Using the address-of operator on a slice gives a single-item pointer,
    // while using the `ptr` field gives a many-item pointer.
    try expect(@TypeOf(slice.ptr) == [*]i32);
    try expect(@TypeOf(&slice[0]) == *i32);
    try expect(@ptrToInt(slice.ptr) == @ptrToInt(&slice[0]));

    // Slices have array bounds checking. If you try to access something out
    // of bounds, you'll get a safety check failure:
    slice[10] += 1;

    // Note that `slice.ptr` does not invoke safety checking, while `&slice[0]`
    // asserts that the slice has len >= 1.
}

Shell

$ zig test test.zig
1/1 test "basic slices"... thread 792342 panic: index out of bounds
/home/andy/Downloads/zig/docgen_tmp/test.zig:22:10: 0x207c83 in test "basic slices" (test)
    slice[10] += 1;
         ^
/home/andy/Downloads/zig/lib/std/special/test_runner.zig:80:28: 0x22f6d3 in std.special.main (test)
        } else test_fn.func();
                           ^
/home/andy/Downloads/zig/lib/std/start.zig:543:22: 0x2280cc in std.start.callMain (test)
            root.main();
                     ^
/home/andy/Downloads/zig/lib/std/start.zig:495:12: 0x20953e in std.start.callMainWithArgs (test)
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/home/andy/Downloads/zig/lib/std/start.zig:409:17: 0x2085d6 in std.start.posixCallMainAndExit (test)
    std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
                ^
/home/andy/Downloads/zig/lib/std/start.zig:322:5: 0x2083e2 in std.start._start (test)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
error: the following test command crashed:
docgen_tmp/zig-cache/o/8e4964e05ddaf866e77360a551b3c05b/test /home/andy/Downloads/zig/build-release/zig

This is one reason we prefer slices to pointers.

slices.zig

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

test "using slices for strings" {
    // Zig has no concept of strings. String literals are const pointers
    // to null-terminated arrays of u8, and by convention parameters
    // that are "strings" are expected to be UTF-8 encoded slices of u8.
    // Here we coerce *const [5:0]u8 and *const [6:0]u8 to []const u8
    const hello: []const u8 = "hello";
    const world: []const u8 = "世界";

    var all_together: [100]u8 = undefined;
    // You can use slice syntax on an array to convert an array into a slice.
    const all_together_slice = all_together[0..];
    // String concatenation example.
    const hello_world = try fmt.bufPrint(all_together_slice, "{s} {s}", .{ hello, world });

    // Generally, you can use UTF-8 and not worry about whether something is a
    // string. If you don't need to deal with individual characters, no need
    // to decode.
    try expect(mem.eql(u8, hello_world, "hello 世界"));
}

test "slice pointer" {
    var array: [10]u8 = undefined;
    const ptr = &array;

    // You can use slicing syntax to convert a pointer into a slice:
    const slice = ptr[0..5];
    slice[2] = 3;
    try expect(slice[2] == 3);
    // The slice is mutable because we sliced a mutable pointer.
    // Furthermore, it is actually a pointer to an array, since the start
    // and end indexes were both comptime-known.
    try expect(@TypeOf(slice) == *[5]u8);

    // You can also slice a slice:
    const slice2 = slice[2..3];
    try expect(slice2.len == 1);
    try expect(slice2[0] == 3);
}

Shell

$ zig test slices.zig
1/2 test "using slices for strings"... OK
2/2 test "slice pointer"... OK
All 2 tests passed.

See also:

  • Pointers
  • for
  • Arrays

Sentinel-Terminated Slices

The syntax [:x]T is a slice which has a runtime known length and also guarantees a sentinel value at the element indexed by the length. The type does not guarantee that there are no sentinel elements before that. Sentinel-terminated slices allow element access to the len index.

null_terminated_slice.zig

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

test "null terminated slice" {
    const slice: [:0]const u8 = "hello";

    try expect(slice.len == 5);
    try expect(slice[5] == 0);
}

Shell

$ zig test null_terminated_slice.zig
1/1 test "null terminated slice"... OK
All 1 tests passed.

Sentinel-terminated slices can also be created using a variation of the slice syntax data[start..end :x], where data is a many-item pointer, array or slice and x is the sentinel value.

null_terminated_slicing.zig

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

test "null terminated slicing" {
    var array = [_]u8{ 3, 2, 1, 0, 3, 2, 1, 0 };
    var runtime_length: usize = 3;
    const slice = array[0..runtime_length :0];

    try expect(@TypeOf(slice) == [:0]u8);
    try expect(slice.len == 3);
}

Shell

$ zig test null_terminated_slicing.zig
1/1 test "null terminated slicing"... OK
All 1 tests passed.

Sentinel-terminated slicing asserts that the element in the sentinel position of the backing data is actually the sentinel value. If this is not the case, safety-protected Undefined Behavior results.

test.zig

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

test "sentinel mismatch" {
    var array = [_]u8{ 3, 2, 1, 0 };

    // Creating a sentinel-terminated slice from the array with a length of 2
    // will result in the value `1` occupying the sentinel element position.
    // This does not match the indicated sentinel value of `0` and will lead
    // to a runtime panic.
    var runtime_length: usize = 2;
    const slice = array[0..runtime_length :0];

    _ = slice;
}

Shell

$ zig test test.zig
1/1 test "sentinel mismatch"... thread 792492 panic: sentinel mismatch
/home/andy/Downloads/zig/docgen_tmp/test.zig:12:24: 0x207a3a in test "sentinel mismatch" (test)
    const slice = array[0..runtime_length :0];
                       ^
/home/andy/Downloads/zig/lib/std/special/test_runner.zig:80:28: 0x22f463 in std.special.main (test)
        } else test_fn.func();
                           ^
/home/andy/Downloads/zig/lib/std/start.zig:543:22: 0x227e5c in std.start.callMain (test)
            root.main();
                     ^
/home/andy/Downloads/zig/lib/std/start.zig:495:12: 0x20927e in std.start.callMainWithArgs (test)
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/home/andy/Downloads/zig/lib/std/start.zig:409:17: 0x208316 in std.start.posixCallMainAndExit (test)
    std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
                ^
/home/andy/Downloads/zig/lib/std/start.zig:322:5: 0x208122 in std.start._start (test)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
error: the following test command crashed:
docgen_tmp/zig-cache/o/8e4964e05ddaf866e77360a551b3c05b/test /home/andy/Downloads/zig/build-release/zig

See also:

  • Sentinel-Terminated Pointers
  • Sentinel-Terminated Arrays