The following question is entirely based on curiosity. I don’t really have a real-world problem where I currently need this, but I’m curious if there is a solution in Zig for it.
Zig supports multidimensional arrays. Also, it is possible to have zero-sized arrays. Now what if we have two (or more) dimensional arrays where one dimension has a size of zero?
Consider:
const std = @import("std");
pub fn printDim(matrix: anytype) void {
std.debug.print("{}x{}\n", .{ matrix.len, matrix[0].len });
}
pub fn main() void {
const x: [17][0]i32 = undefined;
printDim(x);
}
Output:
17x0
Note that this concept also exists in other languages. Let’s see how Octave deals with it (empty matrices in Octave):
$ octave
GNU Octave, version 10.2.0
…
octave:1> A = zeros(17, 0)
A = [](17x0)
octave:2> rows(A)
ans = 17
octave:3> columns(A)
ans = 0
octave:4> numel(A)
ans = 0
octave:5> B = zeros(0, 17)
B = [](0x17)
octave:6> rows(B)
ans = 0
octave:7> columns(B)
ans = 17
octave:8> numel(B)
ans = 0
octave:9> A == B
error: mx_el_eq: nonconformant arguments (op1 is 17x0, op2 is 0x17)
But getting back to Zig, let’s try:
- const x: [17][0]i32 = undefined;
+ const x: [0][17]i32 = undefined;
Now it fails to work as we get:
multiary.zig:4:53: error: indexing into empty array is not allowed
std.debug.print("{}x{}\n", .{ matrix.len, matrix[0].len });
~~~~~~^~~
So I wonder:
- Are zero sized one-dimensional arrays used in Zig?
- How about two-dimensional arrays where only one dimension (possibly the first?) has a size of zero? Are those used as well?
- Is this a supported feature, or just a curious corner case that one shouldn’t rely on?
- If I “initialize” a zero sized array (with one or more than one dimension) with
undefinied, is it then properly initialized?
- Can I reliably obtain the column count
nof an m x n matrix that is represented as[m][n]Tin Zig (for allm >= 0)?- If yes, how?
- If not, would this be a nice-to-have?
Maybe a possible solution is to use a dedicated type (constructor), instead of the built-in arrays:
const std = @import("std");
pub fn Matrix(
comptime T: type,
comptime rows: usize,
comptime cols: usize,
) type {
return struct {
values: [rows][cols]T,
const row_count = rows;
const col_count = cols;
// Alternatively also:
pub fn rowCount(self: @This()) usize {
_ = self;
return row_count;
}
pub fn colCount(self: @This()) usize {
_ = self;
return col_count;
}
};
}
pub fn printDim(matrix: anytype) void {
std.debug.print("{}x{}\n", .{
@TypeOf(matrix).row_count,
@TypeOf(matrix).col_count,
});
// Alternatively:
std.debug.print("{}x{}\n", .{
matrix.rowCount(),
matrix.colCount(),
});
}
pub fn main() void {
const a: Matrix(i32, 17, 0) = undefined;
printDim(a);
const b: Matrix(i32, 0, 17) = undefined;
printDim(b);
}
Output:
17x0
17x0
0x17
0x17
That’s a bit clunky though, yet cleaner than matrix[0].len (besides working for zero sized arrays).
So final question:
- For non-zero sized two-dimensional arrays, is it idiomatic to write
matrix[0].lento obtain the size of the second dimension?
What do you think?