You can also simplify @david_vanderson’s solution. The compiler is able to infer the array types from the result type, so you can use .
instead of explicitly naming the type in every line:
const accounts: []const []const i8 = &.{
&.{ 1, 2 },
&.{ 3, 4 },
};
But keep in mind that with slices this will be stored quite inefficiently. Here is roughly how this would look in memory:
accounts = .{.ptr = 0xabcd, .len = 2}
0xabcd = .{.ptr = 0xbcde, .len = 2, .ptr = 0xcdef, .len = 2}
0xbcde = .{1, 2}
0xcdef = .{3, 4}
That’s why in practice I would recommend to make a struct that makes sure everything is flat in one array. This is what I use when the array length is runtime-known:
pub fn Array2D(comptime T: type) type {
return struct {
const Self = @This();
mem: []T,
width: u32,
height: u32,
pub fn init(allocator: Allocator, width: u32, height: u32) !Self {
return .{
.mem = try allocator.alloc(T, width*height),
.width = width,
.height = height,
};
}
pub fn deinit(self: Self, allocator: Allocator) void {
allocator.free(self.mem);
}
pub fn get(self: Self, x: usize, y: usize) T {
std.debug.assert(x < self.width and y < self.height);
return self.mem[x*self.height + y];
}
pub fn getRow(self: Self, x: usize) []T {
std.debug.assert(x < self.width);
return self.mem[x*self.height..][0..self.height];
}
pub fn set(self: Self, x: usize, y: usize, t: T) void {
std.debug.assert(x < self.width and y < self.height);
self.mem[x*self.height + y] = t;
}
pub fn ptr(self: Self, x: usize, y: usize) *T {
std.debug.assert(x < self.width and y < self.height);
return &self.mem[x*self.height + y];
}
};
}
The memory layout of this is much better:
accounts = .{.mem.ptr = 0xabcd, .mem.len = 4, width = 2, .height = 2}
0xabcd = .{1, 2, 3, 4}