By idiomatic, I am referring to the way a zig programmer would expect them to work. I have the following matrix structure:
pub fn Matrix(comptime T: type) type {
const _supported = whatSupportedNumericType(T);
if (_supported == SupportedNumericType.Unsupported) {
("Unsupported type");
}
return struct {
const Self = ();
data: []T,
rows: usize,
cols: usize,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, rows: usize, cols: usize) !Self {
if (rows == 0 or cols == 0) {
return std.debug.panic("Matrix dimensions must be greater than zero", .{});
}
return Self{
.data = try allocator.alloc(T, rows * cols),
.rows = rows,
.cols = cols,
.allocator = allocator,
};
}
pub fn set(self: *Self, row: usize, col: usize, value: T) void {
const supported = whatSupportedNumericType(T);
const index: usize = row * self.cols + col;
if (supported == SupportedNumericType.Custom) {
if (self.data[index]) {
self.data[index].deinit();
}
}
self.data[index] = value;
}
pub fn get(self: *const Self, row: usize, col: usize) T {
return self.data[row * self.cols + col];
}
};
}
There are more functions, but I just put the simple ones so you get an idea. Also, the supported construct is not relevant for my questions.
There are two functions that I want to make (more but they follow the same template) and don’t know how a zig programmer would expect them to work:
- Add function:
My initial idea (as I had it done in C) was to have something like
pub fn add(allocator: ?std.mem.Allocator, left: *const Self, right: *const Self, out: *Self) !void
where if allocator was null
, the function would expect out to be initiallized, but if it held an actual allocator, then it would initialize it. Then I began looking at the source code of the standard library and found that Mutable
, an arbritrary precision integer, had the add
function defined as
pub fn add(r: *Mutable, a: Const, b: Const) void
where Const is a different arbitrary precision integer. So then, would you, as a zig programmer expect more the function to be defined as
pub fn add(self: *Self, left: Self, right: Self) !void
? Another way I found was
pub fn add(allocator: std.mem.Allocator, left: Self, right: Self) !Self
as is done with the Complex
type. So, which approach is more “idiomatic” zig? Also, if you have other proposals, feel free to suggest them, I am only a begginer using zig.
- Submatrix function:
This function would essentially “extract” or “create” a matrix with the desired rows and columns of another matrix, like what A[[1, 4, 2],[3, 1, 1, 5]]
would do in MATLAB. So, again, my first idea was to do it as I had it done in C,
pub fn submatrix(allocator: ?std.mem.Allocator, M: *const Self, rows: []const usize, cols: []const usize, out: *Self) !void
following the same criteria for the allocator as the add
function. Then I decided to make an
pub fn initSubmatrix(allocator: std.mem.Allocator, src: Self, rows: []const usize, cols: []const usize) !Self
which, as you might expect, initializes a new matrix as the submatrix of src
(you can also give me suggestions about this function), but I also want the standard submatrix
function. So, like with add
, how would you expect this function to work, and what order of arguments would you expect?
Another question I have about how zig is programmed is that, while reading some of the standard library, I have encountered very little use of pointers for function parameters, even with const
, unless that specific parameter is edited by the function. From my perspective, it doesn’t seem very efficient as you are making a complete copy of the argument (obviously a shallow copy). So why is this?
I want to thank dearly anyone who takes the time to read this and answer my questions. Have a pleasant day.
kupper is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.