That would be fairly inconvenient:
test "empty sentinel" {
const empty = "";
// -> *const [0:0]u8
std.debug.print("{}\n", .{@TypeOf(empty)});
// -> 0
std.debug.print("{d}\n", .{empty[0]});
}
The key difference between a sentinel-terminated array and a normal one, is that it’s legal to read sentinel[sentinel.len]. This is crucial for C interop.
If this is cast to a slice, then no, you can’t read anything from it anymore. But the null byte is still there, and reading it directly is harmless, and allowed in Zig as well if you cast to a sentinel terminated slice:
test "empty sentinel" {
const empty = "";
const empty_sentinel_slice: [:0]const u8 = empty;
// -> [:0]const u8
std.debug.print("{}\n", .{@TypeOf(empty_sentinel_slice)});
// C interop version: -> 0
std.debug.print("{d}\n", .{empty_sentinel_slice.ptr[0]});
// Also legal Zig: -> 0
std.debug.print("{d}\n", .{empty_sentinel_slice[0]});
const empty_slice: []const u8 = empty;
// error: indexing into empty slice is not allowed
// std.debug.print("{d}", .{empty_slice[0]});
// But this is still allowed: -> 0
std.debug.print("{d}\n", .{empty_slice.ptr[0]});
}
So by construction, a sentinel-terminated slice is never empty: a sentinel-terminated array always has at least one accessible element, coercing it to a slice hides that element, but it’s still there, and it’s still defined behavior to read it.
If you try and make a zero-width array into a sentinel-terminated slice, that doesn’t compile:
test "truly empty sentinel?" {
const really_empty: [0]u8 = .{};
// error: expected type '[:0]u8', found '*const [0]u8'
// note: destination pointer requires '0' sentinel
const very_empty_sentinel_slice: [:0]u8 = &really_empty;
// never gets here
std.debug.print("? {d}\n", .{very_empty_sentinel_slice[0]});
}
I haven’t been able to find a way to make a sentinel-terminated array or slice which isn’t actually sentinel-terminated (undefined doesn’t count), and that’s good, because it would undermine the type system fairly badly to have a genuinely zero-width sentinel. I would read [0:0] as “zero data, one sentinel 0”, the length is of the data but the sentinel is always there.
I may have missed a way of constructing an actually-zero-width sentinel, though.