It is often worth it to use way more memory than you need to if that memory can remain on the stack.
Hard to leak memory that you never allocate. Use std.BoundedArray
and it's assumeCapacity
methods
alot. Operating systems have a max file path. If I'm able to put that many bytes on the stack
I should never have to return OutOfMemory
, unless it's an invalid value altogether
var source = std.BoundedArray(u8, std.fs.MAX_PATH_BYTES).init(0) catch unreachable;
try switch (source_json) {
.string => |str| {
if (str.len > source.capacity()) return error.InvalidField;
source.appendSliceAssumeCapacity(str);
},
else => error.InvalidField,
};
For operations on nested data structures, it's often insufficient to just return an error code like MissingField
and InvalidField
. Pass a context stack to a function that may error in this way so it can push/pop the path it traverses. Once again, using std.BoundedArray
and reasonable defaults to avoid a function that requires memory allocation
// Context.zig
const std = @import("std");
const Self = @This();
const ContextSegment = std.BoundedArray(u8, 256);
context: std.BoundedArray(ContextSegment, 12),
pub fn init() Self {
return Self{
.context = std.BoundedArray(ContextSegment, 8).init(0) catch unreachable,
};
}
pub fn push(self: *Self, comptime fmt_str: []const u8, args: anytype) void {
const context_segment: *ContextSegment = self.context.addOne() catch return;
var buffer: [256]u8 = undefined;
const output_buff = std.fmt.bufPrint(&buffer, fmt_str, args) catch buffer[0..];
context_segment.resize(output_buff.len) catch unreachable;
@memcpy(context_segment.slice(), output_buff);
}
pub fn pop(self: *Self) void {
_ = self.context.pop();
}
A print function for this is pretty simple:
pub fn print(self: *Self) std.BoundedArray(u8, 4096) {
var output = std.BoundedArray(u8, 4096).init(0) catch unreachable;
for (0..self.context.len) |i| {
const idx = self.context.len - i - 1;
const context_segment: ContextSegment = self.context.slice()[idx];
output.appendSlice(context_segment.slice()) catch {};
if (idx != 0) ba.appendSlice(" <- ") catch {};
}
return output;
}
Now we have some super nice error handling
fn loadMap(
l: *const Logger,
map_id: MapId,
) !void {
var context = Context.init();
errdefer |err| {
l.write("{}: {s}\n", .{ err, context.print().slice() });
}
_ = try TileMap.init(map_id, &context);
}