This weekend I wrote my first zig code. And while it only amounted to less than 100 lines of code, I learned a lot from this trivial exercise—coming from the world of garbage collected languages. I was hoping for some validation of my thought process to ensure I’m correctly thinking about memory management. Please note this is just a toy for myself to learn from and it is not complete.
I wrote two versions of a simple utility function to make an HTTP request that parses the resulting JSON into the supplied type. This is something I do often so I wanted to simplify the process a bit. As an example, if I wanted to parse some of the output from httpbin.org, I would do the following:
const url = "http://httpbin.org/anything";
const HttpBinResponse = struct {
method: []const u8,
origin: []const u8,
url: []const u8,
headers: std.json.Value,
};
pub fn main() !void {
var gpa = std.heap.DebugAllocator(.{}).init;
defer _ = gpa.detectLeaks();
const allocator = gpa.allocator();
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const result = try jsonfetch.fetch2(&client, url, *HttpBinResponse);
defer result.deinit();
// Now use result.value.? which is a HttpBinResponse value.
}
The code can be found here. In root.zig, you’ll see two versions of JsonResult and fetch methods. The questions I have and would appreciate feedback on:
- Is my thought process about conserving the extra allocation when parsing the JSON valid (see comment blocks in
root.zig)? With a small change, I was able to avoid that extra allocation at the cost of some extra “private” fields in my result struct. - Is there an allocator that will tell me how many bytes have been requested over the lifetime of execution? I wanted to validate that version 2 does save a couple of allocations. I thought
total_requested_bytesinDebugAllocatorwas what I was looking for, but it always printed zero. When looking at the code, I think this is the total outstanding bytes that have not been freed. So then I asked a couple of LLMs to write a tracking allocator and they both failed. Anyone have a simple way that I can use to validate number of bytes being allocated? - How can one find out the full list of possible errors that can be returned from a function? For example, in my fetch functions, I just use
!JsonResult, but I’m guessing it’s better form to explicitly list what could fail? Or is it better to catch most of them and wrap them somehow into errors tailored for my function. How does one wrap errors?
My overall thoughts about zig after this weekend is that it is a lot of fun to write. In this simple project I was able to use comptime, optional types, error handling, pointers, etc … And reading the standard library is not daunting like it can be in some languages (i.e. rust, just reading rust standard library docs is daunting frankly). I’m really enjoying my zig adventure so far!