Reading integers from dynamic address

I’ve got a project where I’m parsing headers from a binary file. So far I’ve been reading from static addresses like so:

        self.page_size = std.mem.readInt(u16, buf[16..18], .big);
        self.file_change_counter = std.mem.readInt(u32, buf[24..28], .big);
        self.num_pages = std.mem.readInt(u32, buf[28..32], .big);

This has worked well. However now I need to parse a dynamic number of integers into a buffer, which I am doing like so:

        while (i < self.page_header.num_cells) : (i += 1) {
            const slice_start = start_idx + i * 2;
            const slice_end = slice_start + 2;
            cells[i] = std.mem.readInt(u16, self.page_buf[slice_start..slice_end], .big);
        }

However this yields to a type error that I don’t really understand:

src/db.zig:63:58: error: expected type '*const [2]u8', found '[]u8'
            cells[i] = std.mem.readInt(u16, self.page_buf[slice_start..slice_end], .big);
                                            ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~
/home/axelmagn/.zvm/master/lib/std/mem.zig:1797:49: note: parameter type declared here
pub inline fn readInt(comptime T: type, buffer: *const [@divExact(@typeInfo(T).int.bits, 8)]u8, endian: Endian) T {
                                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I don’t understand:

  1. Why does this work for literal indices but not variable indices?
  2. Is it possible to coerce a slice into *const [2]u8?
  3. If so, how?

I believe you could do:

const slice = self.page_buf[slice_start..slice_end];
cells[i] = std.mem.readInt(u16, slice[0..2], .big);

Another option:

std.mem.readInt(u16, &.{ slice[0], slice[1] }, .big);

I think the issue relates to the slice length being known at compile time, and slice_start is only known at runtime.

slice[start..][..2] also works

readInt takes a pointer to an array to enforce at compile time the length of the data. When slicing with comp-time known length, it actually returns a pointer to an array instead of a slice

Thanks. that makes a lot of sense in retrospect.

Only replying since I also ran into this, but I’m surprised the compiler can’t, in some instances, still have it work. E.g.

const size = std.mem.readInt(u32, buffer[idx..(idx + 4)], .little);

i.e. the “length” is comptime known, even if the start and end of it are only run-time known. But that also prseently yields this issue.

You should be able to use:

const size = std.mem.readInt(u32, buffer[idx..][0..4], .little);

ah but its not, the addition may overflow! Of course, with normal addition that is illegal so the compiler might be justified in being smart, but think about how that would work!

Is addition in the end position of slicing special when it includes the start position and a comptime known other operand? That would be inconsistent with the rest of the language.

Or does the compiler front end track such nuanced invariants across the whole language? That would make the compiler much more complicated, and certainly slower (but idk how much).

Both would need to be in the language spec as they affect types, but the latter would be pervasive across the entire spec as it would affect comptime evaluation. Either way it complicates the language which is an anti-goal.

Also being forced to use the [s..][0..N] syntax makes it explicit that it is an array pointer instead of a slice.