Skip to content

Instantly share code, notes, and snippets.

@abradley2
Last active February 17, 2025 13:08
Show Gist options
  • Save abradley2/ba89c52b9e3cd60d4a116dd1e3382375 to your computer and use it in GitHub Desktop.
Save abradley2/ba89c52b9e3cd60d4a116dd1e3382375 to your computer and use it in GitHub Desktop.
Tony's Zig Patterns

Use std.BoundedArray alot

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,
};

Use stacks to provide context for errors

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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment