The purpose of a sentinel (such as the 0 in a C string) is to mark the end of the sequence of elements. You obtain the length/end of the sequence by counting each element in a loop until you encounter the sentinel.
Slices carry both a length and a pointer with them at all times, so since you already have a length and know where the sequence ends, sentinel-terminated slices are more or less unnecessary in pure Zig code. But they are useful if you have a slice that will eventually be passed to a C API that expects a sentinel-terminated pointer (such as a 0-terminated string).
With a const u8 slice there’s no guarantee that the string will have a terminating 0 byte, so you would need to make a copy of it and append a final 0 byte (usually with Allocator.dupeZ) if you wanted to pass it to a C API. But with [:0]const u8 you already know for sure that there’s a terminating 0 byte, so there’s no need for the copy.
The safety is enforced via compile errors. If you have a function with a parameter of type [*:0]const u8, passing a pointer of type [*]const u8 (note: no sentinel) to it is a compile error.
But, if you were to cast the [*]const u8 pointer to [*:0]const u8 using @ptrCast, the compiler won’t insert any code that checks that the pointer actually contains a 0 byte (you are more or less telling the compiler that “I know better”), so this could potentially be unsafe and result in a buffer overflow.
I think this a great topic for learning Zig, even at the early stages. It should be emphasized how C does things, versus how Zig does things, with some clear guidance on interop. Null-terminated pointers are critical when working with C and character arrays, so even though it’s been de-emphasized in Zig, I think there is still a lot of value there for understanding how Zig works in general.
This argument while true theoretically is a very bad advice in practice. 50 years of C taught us the null terminated arrays are major cause of serious security bugs. Also, getting length of the null-terminated array is O(n) runtime versus O(1) for a slice. Last but not least, memory savings from not storing length field is rarely realized, for allocator’s alignment will pad null byte all way up to 4 or 8 bytes, the same size as the lenght field.
TL;DR: Use null terminated arryas (pointers, slices) only when you absolutely have to (interface to C). Use slices in all other cases.
I disagree that it always bad. O(n) length check is only a problem for very large arrays and if you aren’t modifying the array after you create it then it’s almost impossible to introduce security bugs. Additionally, Zig’s sentinel system and various stdlib functions for working with sentinel terminated slices make it far safer than in C.