Some arrays have a special value after the last ordinary element. This value is called a sentinel.
Some arrays have a special value after the last ordinary element. This value is called a sentinel.
A sentinel array type writes the sentinel between the length and the element type:
[3:0]u8This means:
an array of 3 u8 values, followed by a 0 sentinelThe sentinel is not counted in the array length.
const std = @import("std");
pub fn main() void {
const name: [3:0]u8 = .{ 'z', 'i', 'g' };
std.debug.print("{d}\n", .{name.len});
}The output is:
3The array has three ordinary elements. The sentinel exists after them.
You can see the ordinary elements by indexing:
const std = @import("std");
pub fn main() void {
const name: [3:0]u8 = .{ 'z', 'i', 'g' };
std.debug.print("{c}\n", .{name[0]});
std.debug.print("{c}\n", .{name[1]});
std.debug.print("{c}\n", .{name[2]});
}The output is:
z
i
gThe sentinel is useful when working with C strings. C strings end with a zero byte. Zig can express that fact in the type.
A string literal has a zero sentinel:
const s = "zig";Its type is close to:
*const [3:0]u8It points to an array of three bytes, followed by a zero byte.
This is why a string literal can be passed to some APIs that expect sentinel-terminated data.
You can slice with a sentinel too:
const std = @import("std");
pub fn main() void {
const name: [3:0]u8 = .{ 'z', 'i', 'g' };
const s: [:0]const u8 = name[0..];
std.debug.print("{s}\n", .{s});
}The type:
[:0]const u8means:
a slice of const u8 values, with a 0 sentinel after the sliceThis is different from:
[]const u8A normal slice has a pointer and a length. It does not promise any value after the last element.
A sentinel slice has a pointer, a length, and a promise that the sentinel value is present at the end.
The sentinel is checked when the compiler can check it. If Zig cannot prove the sentinel exists, you must use an operation that verifies it or provide data whose type already carries the sentinel.
Sentinels are not limited to zero. This array uses 255 as its sentinel:
const bytes: [4:255]u8 = .{ 1, 2, 3, 4 };Most programs use zero sentinels because C strings use zero.
A sentinel array is still an array. It can be copied, indexed, and passed by pointer.
const std = @import("std");
fn printName(name: [:0]const u8) void {
std.debug.print("{s}\n", .{name});
}
pub fn main() void {
const name: [3:0]u8 = .{ 'z', 'i', 'g' };
printName(name[0..]);
}The output is:
zigUse sentinel arrays when the end marker is part of the data contract. For ordinary buffers, use normal arrays and slices.
Exercises.
Exercise 6-13. Declare a [5:0]u8 array containing the letters h, e, l, l, o.
Exercise 6-14. Print its .len.
Exercise 6-15. Convert it to a [:0]const u8 slice and print it with {s}.
Exercise 6-16. Declare a sentinel array that uses 255 as the sentinel.