arraysslicesentinelzig

Zig slice of sentinel-terminated array excluding all trailing sentinels


Given a sentinel-terminated array [N:0]T which is only partially filled at the beginning of the array with and unknown amount of entities n < N, and the rest is filled with sentinels (0), what's the most concise way to grab a slice only containing the first entities before any sentinel?

Here's a code example in linux, where utsname.release: [64:0]u8 is populated by the OS with the kernel's semantic version string.

const std = @import("std");

// ...

    const utsname: std.os.linux.utsname = std.os.uname();
    std.debug.print("utsname.release = '{s}'\n", .{utsname.release});
    std.debug.print("utsname.release.len = {}\n", .{utsname.release.len});
    const utsNameReleaseSlice: []const u8 = &utsname.release;
    std.debug.print("utsNameReleaseSlice = '{s}'\n", .{utsNameReleaseSlice});
    std.debug.print("utsNameReleaseSlice.len = {}\n", .{utsNameReleaseSlice.len});

output:

utsname.release = '6.6.8-gnu'
utsname.release.len = 64
utsNameReleaseSlice = '6.6.8-gnu'
utsNameReleaseSlice.len = 64

In the print above, the string looks ok but it prints out the sentinels which are zero-width characters. When I provide the slice to std.SemanticVersion.parse it throws an error when it gets to those sentinel characters.

To fix this I got the index of the first sentinel and took a slice up to there:

    const versionStringEnd: usize = std.mem.indexOf(u8, &utsname.release, &[_]u8{0}) orelse utsname.release.len;
    const utsNameReleaseSlice: []const u8 = utsname.release[0..versionStringEnd];
    std.debug.print("utsNameReleaseSlice = '{s}'\n", .{utsNameReleaseSlice});
    std.debug.print("utsNameReleaseSlice.len = {}\n", .{utsNameReleaseSlice.len});

output:

utsNameReleaseSlice = '6.6.8-gnu'
utsNameReleaseSlice.len = 9

Is there a more concise way of getting this slice which ignores sentinels?


Solution

  • You're looking for std.mem.span, which does exactly that.

    Specifically, with the above example, you would need to cast the sentinel-terminated array as a sentinel-terminated pointer @as([*:0]const u8, &utsname.release) and provide that to std.mem.span:

    const utsNameReleaseSlice: []const u8 = std.mem.span(@as([*:0]const u8, &utsname.release));