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.