Is this a reasonable way to construct a dynamically sized matrix?

I came up with the following horrorkludge answering the latest “How to allocate N-dimensional array?” on Discord:

fn matrix(T: type, rows: usize, cols: usize, allocator: std.mem.Allocator) ![][]T {
    const n = rows * (@sizeOf([]T) + cols * @sizeOf(T));
    const m = try allocator.allocWithOptions(u8, n, .of([]T), null);

    const rowSlices = std.mem.bytesAsSlice([]T, m);
    const data: []T = @alignCast(std.mem.bytesAsSlice(T, m[rows * @sizeOf([]T) ..]));

    for (0..rows) |rowIndex| {
        rowSlices[rowIndex] = data[rowIndex * cols ..][0..cols];
    }
    return rowSlices;
}

It sort of does what you’d expect:

  • it returns a slice of slices which you can free when you’re done with it and index by row and column
  • it checks bounds (except it panics with “General protection exception (no address available)” if the first index is >= rows)

So, brilliant or belongs in the bin?

I think it’s better to allocate a single one dimensional slice where all your data lives and then do index arithmetics to get the elements. Check how Zignal does it. Look at the init and at methods.

To be clear, that was my initial suggestion as well because I use numpy a lot but the OP was really asking about [][] indexing

1 Like

Sorry, I should’ve been more specific when I said “I think it’s better”.

In the suggested approach, despite the data being contiguous in memory, the access is not, which might introduce cache misses and prevent SIMD optimizations.

Moreover, there’s an extra overhead per row, since we store the ptr and the len for each one. It might be prohibitive if you have many small matrices.

That being said, being able to use the [][] syntax for indexing is cool, though.

1 Like

So, brilliant or belongs in the bin?

Both, I guess?

I mean, it’s cool that you did come up with a solution that conforms to the requirement of being able to index with [][]. At the same time, it incurs possibly non-negligible performance costs, as @adria explains.

I think clinging to the [][] syntax is misguided (for Zig, which intentionally does not have operator overloading). Just define a struct with a method that takes the two indices and does the arithmetics for you. Zignal’s Matrix linked above (specifically the at() method) looks like a good example.

2 Likes

I think clinging to the [][] syntax is misguided

What do you mean? It already exists, no?

I mean in this particular case in Zig. It just does not apply cleanly and slows things down just for the sake of slightly “nicer” syntax.

1 Like

Oh you mean this implementation. But the original motive was to make it possible to cast [M*N]u8` to [][N]u8 (with runtime known values) so that you could index it like a matrix. Like you can do in C:


    int* buffer = malloc(sizeof(int) * N * M);
    int(*matrix)[M] = (int(*)[M])buffer;
    matrix[2][4] = 123;