Slices

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.

test_basic_slices.zig

  1. const expect = @import("std").testing.expect;
  2. test "basic slices" {
  3. var array = [_]i32{ 1, 2, 3, 4 };
  4. var known_at_runtime_zero: usize = 0;
  5. _ = &known_at_runtime_zero;
  6. const slice = array[known_at_runtime_zero..array.len];
  7. try expect(@TypeOf(slice) == []i32);
  8. try expect(&slice[0] == &array[0]);
  9. try expect(slice.len == array.len);
  10. // If you slice with comptime-known start and end positions, the result is
  11. // a pointer to an array, rather than a slice.
  12. const array_ptr = array[0..array.len];
  13. try expect(@TypeOf(array_ptr) == *[array.len]i32);
  14. // You can perform a slice-by-length by slicing twice. This allows the compiler
  15. // to perform some optimisations like recognising a comptime-known length when
  16. // the start position is only known at runtime.
  17. var runtime_start: usize = 1;
  18. _ = &runtime_start;
  19. const length = 2;
  20. const array_ptr_len = array[runtime_start..][0..length];
  21. try expect(@TypeOf(array_ptr_len) == *[length]i32);
  22. // Using the address-of operator on a slice gives a single-item pointer.
  23. try expect(@TypeOf(&slice[0]) == *i32);
  24. // Using the `ptr` field gives a many-item pointer.
  25. try expect(@TypeOf(slice.ptr) == [*]i32);
  26. try expect(@intFromPtr(slice.ptr) == @intFromPtr(&slice[0]));
  27. // Slices have array bounds checking. If you try to access something out
  28. // of bounds, you'll get a safety check failure:
  29. slice[10] += 1;
  30. // Note that `slice.ptr` does not invoke safety checking, while `&slice[0]`
  31. // asserts that the slice has len > 0.
  32. }

Shell

  1. $ zig test test_basic_slices.zig
  2. 1/1 test_basic_slices.test.basic slices... thread 986458 panic: index out of bounds: index 10, len 4
  3. /home/ci/actions-runner/_work/zig-bootstrap/zig/docgen_tmp/test_basic_slices.zig:34:10: 0x1039439 in test.basic slices (test)
  4. slice[10] += 1;
  5. ^
  6. /home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:158:25: 0x1044ad2 in mainTerminal (test)
  7. if (test_fn.func()) |_| {
  8. ^
  9. /home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:35:28: 0x103ad3b in main (test)
  10. return mainTerminal();
  11. ^
  12. /home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:501:22: 0x1039a19 in posixCallMainAndExit (test)
  13. root.main();
  14. ^
  15. /home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:253:5: 0x1039581 in _start (test)
  16. asm volatile (switch (native_arch) {
  17. ^
  18. ???:?:?: 0x0 in ??? (???)
  19. error: the following test command crashed:
  20. /home/ci/actions-runner/_work/zig-bootstrap/out/zig-local-cache/o/5a09a80de513b3991a6774a68dfe69af/test

This is one reason we prefer slices to pointers.

test_slices.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. const mem = std.mem;
  4. const fmt = std.fmt;
  5. test "using slices for strings" {
  6. // Zig has no concept of strings. String literals are const pointers
  7. // to null-terminated arrays of u8, and by convention parameters
  8. // that are "strings" are expected to be UTF-8 encoded slices of u8.
  9. // Here we coerce *const [5:0]u8 and *const [6:0]u8 to []const u8
  10. const hello: []const u8 = "hello";
  11. const world: []const u8 = "世界";
  12. var all_together: [100]u8 = undefined;
  13. // You can use slice syntax with at least one runtime-known index on an
  14. // array to convert an array into a slice.
  15. var start: usize = 0;
  16. _ = &start;
  17. const all_together_slice = all_together[start..];
  18. // String concatenation example.
  19. const hello_world = try fmt.bufPrint(all_together_slice, "{s} {s}", .{ hello, world });
  20. // Generally, you can use UTF-8 and not worry about whether something is a
  21. // string. If you don't need to deal with individual characters, no need
  22. // to decode.
  23. try expect(mem.eql(u8, hello_world, "hello 世界"));
  24. }
  25. test "slice pointer" {
  26. var array: [10]u8 = undefined;
  27. const ptr = &array;
  28. try expect(@TypeOf(ptr) == *[10]u8);
  29. // A pointer to an array can be sliced just like an array:
  30. var start: usize = 0;
  31. var end: usize = 5;
  32. _ = .{ &start, &end };
  33. const slice = ptr[start..end];
  34. // The slice is mutable because we sliced a mutable pointer.
  35. try expect(@TypeOf(slice) == []u8);
  36. slice[2] = 3;
  37. try expect(array[2] == 3);
  38. // Again, slicing with comptime-known indexes will produce another pointer
  39. // to an array:
  40. const ptr2 = slice[2..3];
  41. try expect(ptr2.len == 1);
  42. try expect(ptr2[0] == 3);
  43. try expect(@TypeOf(ptr2) == *[1]u8);
  44. }

Shell

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

See also:

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.

test_null_terminated_slice.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. test "0-terminated slice" {
  4. const slice: [:0]const u8 = "hello";
  5. try expect(slice.len == 5);
  6. try expect(slice[5] == 0);
  7. }

Shell

  1. $ zig test test_null_terminated_slice.zig
  2. 1/1 test_null_terminated_slice.test.0-terminated slice... OK
  3. 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.

test_null_terminated_slicing.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. test "0-terminated slicing" {
  4. var array = [_]u8{ 3, 2, 1, 0, 3, 2, 1, 0 };
  5. var runtime_length: usize = 3;
  6. _ = &runtime_length;
  7. const slice = array[0..runtime_length :0];
  8. try expect(@TypeOf(slice) == [:0]u8);
  9. try expect(slice.len == 3);
  10. }

Shell

  1. $ zig test test_null_terminated_slicing.zig
  2. 1/1 test_null_terminated_slicing.test.0-terminated slicing... OK
  3. 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_sentinel_mismatch.zig

  1. const std = @import("std");
  2. const expect = std.testing.expect;
  3. test "sentinel mismatch" {
  4. var array = [_]u8{ 3, 2, 1, 0 };
  5. // Creating a sentinel-terminated slice from the array with a length of 2
  6. // will result in the value `1` occupying the sentinel element position.
  7. // This does not match the indicated sentinel value of `0` and will lead
  8. // to a runtime panic.
  9. var runtime_length: usize = 2;
  10. _ = &runtime_length;
  11. const slice = array[0..runtime_length :0];
  12. _ = slice;
  13. }

Shell

  1. $ zig test test_sentinel_mismatch.zig
  2. 1/1 test_sentinel_mismatch.test.sentinel mismatch... thread 986566 panic: sentinel mismatch: expected 0, found 1
  3. /home/ci/actions-runner/_work/zig-bootstrap/zig/docgen_tmp/test_sentinel_mismatch.zig:13:24: 0x1038f56 in test.sentinel mismatch (test)
  4. const slice = array[0..runtime_length :0];
  5. ^
  6. /home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:158:25: 0x1044aa2 in mainTerminal (test)
  7. if (test_fn.func()) |_| {
  8. ^
  9. /home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:35:28: 0x103aacb in main (test)
  10. return mainTerminal();
  11. ^
  12. /home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:501:22: 0x1039539 in posixCallMainAndExit (test)
  13. root.main();
  14. ^
  15. /home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:253:5: 0x10390a1 in _start (test)
  16. asm volatile (switch (native_arch) {
  17. ^
  18. ???:?:?: 0x0 in ??? (???)
  19. error: the following test command crashed:
  20. /home/ci/actions-runner/_work/zig-bootstrap/out/zig-local-cache/o/50fee1d1f56022c590a3e33bbeb31a11/test

See also: