Error trying to write a u16 into an array

Hello everyone,

At some point in my application I create a buffer that I populate with data before writing the content of the buffer in a file. It’s been working fine so far but for some reason I have a compilation error when trying to write a specific u16 variable into the buffer.

Here is a simplified version of the situation:

const std = @import("std");

pub fn main() !void {
    const little_end = std.builtin.Endian.little;

    var buf: [1024]u8 = undefined;
    const name = "thisisatest";

    std.mem.writeInt(u8, buf[0..1], 10, little_end);
    std.mem.writeInt(u16, buf[1..3], 1000, little_end);

    // in the context of my program, I know for a fact the length is u8 at max
    const name_len: u8 = @intCast(name.len);
    const start_pos_name = 3;
    const end_pos_name = start_pos_name + name_len;
    std.mem.copyForwards(u8, buf[start_pos_name..end_pos_name], name);

    const beg_pos_tag: usize = end_pos_name;
    const end_pos_tag: usize = beg_pos_tag + 2;
    std.mem.writeInt(u16, buf[beg_pos_tag..end_pos_tag], 200, little_end);

    std.debug.print("u8 int: {d}\n", .{buf[0]});
    std.debug.print("u16 int: {d}\n", .{std.mem.readInt(u16, buf[1..3], little_end)});
    std.debug.print("string: {s}\n", .{buf[start_pos_name..end_pos_name]});
    std.debug.print("problem u16: {d}\n", .{std.mem.readInt(u16, buf[beg_pos_tag..end_pos_tag], little_end)});
}

This works fine! It compiles and the output contains what is expected:

u8 int: 10
u16 int: 1000
string: thisisatest
problem u16: 200

Now for the actual code in my application:

        // ... stuff before ...
        const start_pos_name = lgt_fixed_part_thing + 1;
        const end_pos_name: usize = lgt_fixed_part_thing + 1 + thing_name_len;
        std.mem.copyForwards(u8, self.buf_cur_item[start_pos_name..end_pos_name], thing.name);

        for (0..thing.tags.len) |i| {
            const cur_offset = i * 2;
            const beg_pos_tag: usize = lgt_fixed_part_thing + 1 + thing_name_len + cur_offset;
            const end_pos_tag: usize = beg_pos_tag + 2;

            globals.printer.print("Type of end_pos_name: {any} - value: {d}\n", .{ @TypeOf(end_pos_name), end_pos_name });
            globals.printer.print("Type of beg_pos_tag: {any} - value: {d}\n", .{ @TypeOf(beg_pos_tag), beg_pos_tag });
            globals.printer.print("Type of end_pos_tag: {any} - value: {d}\n", .{ @TypeOf(end_pos_tag), end_pos_tag });

            // the problem appears on this line
            std.mem.writeInt(u16, self.buf_cur_item[beg_pos_tag..end_pos_tag], thing.tags[i], little_end);
        }
        // ... stuff after ...

And the compilation error is as follows:

path\data_file_handler.zig:148:52: error: expected type '*[2]u8', found '[]u8'
            std.mem.writeInt(u16, self.buf_cur_item[beg_pos_tag..end_pos_tag], thing.tags[i], little_end);
                                  ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
C:\tools\zig\lib\std\mem.zig:1847:50: note: parameter type declared here
pub inline fn writeInt(comptime T: type, buffer: *[@divExact(@typeInfo(T).int.bits, 8)]u8, value: T, endian: Endian) void {
                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now if I comment the line that does not compile and run the program, I get the following output:

Type of end_pos_name: usize - value: 27
Type of beg_pos_tag: usize - value: 27
Type of end_pos_tag: usize - value: 29
Type of end_pos_name: usize - value: 27
Type of beg_pos_tag: usize - value: 29
Type of end_pos_tag: usize - value: 31

Which is indeed the types and values I expect. So I’m pretty confused as to why the writeInt function does not like them.

When I replace the values of beg_pos_tag and end_pos_tag with hard-coded values like:

const beg_pos_tag: usize = 100;
const end_pos_tag: usize = 102;

It then works. But I don’t understand why.

Could someone explain the reason why it’s not working?

Whether the slice operation buf[start..end] returns a slice with a runtime-known length []u8 or a pointer to an array with a comptime-known length *[2]u8 depends on whether or not the bounds are comptime-known. To illustrate, try compiling the following program:

pub fn main() void {
    var buf = [1]u8{0} ** 16;

    // comptime-known
    const c_start: usize = 4;
    const c_end: usize = c_start + 2;
    const c_slice = buf[c_start..c_end];

    @compileLog(c_start, c_end, c_slice);

    // runtime-known
    var r_start: usize = undefined;
    r_start = 4;
    const r_end: usize = r_start + 2;
    const r_slice = buf[r_start..r_end];

    @compileLog(r_start, r_end, r_slice);
}
Compile Log Output:
@as(usize, 4), @as(usize, 6), @as(*[2]u8, [runtime value])
@as(usize, [runtime value]), @as(usize, [runtime value]), @as([]u8, [runtime value])

Note that the type of the slice is different between the two.

However, there’s a trick you can use to get a slice with a runtime-known start but a comptime-known length to return a pointer to an array: buf[start..][0..length]

This means that if you rewrite the offending line as

std.mem.writeInt(u16, self.buf_cur_item[beg_pos_tag..][0..2], thing.tags[i], little_end);

the slice operation will return a *[2]u8 and the compile error will go away.

2 Likes

end_pos_tag ultimately depends on a runtime value (i), so it is a runtime value itself. When slicing this way, you are losing the comptime information that the slice len is always 2. Instead, you want to do this:

self.buf_cur_item[beg_pos_tag..][0..2]

Note that concatenating slices like this has special handling by the compiler. This way, the compiler will convert the slice to a pointer to a 2-long array.

3 Likes