Hello friends of Zig,
I’m toying with Zig trying to implement Gossip Glomers. I’ve managed to implement the JSON parsing using std.json
but I’m unsure how to structure memory management around it. Maybe my overall approach is wrong, so I’d be happy to hear feedback on this.
The first challenge was that the protocol in maelstrom creates messages like this:
{"src":"n1", dst:"c2", "body": { "type": "init", ...}}
You’ll notice that the body has a type
field and the structure of the body depends on that type. So I’ve created a RawMessage
type:
pub const RawMsg: type = struct {
src: []u8,
dst: []u8,
body: std.json.Value,
}
I can then extract the type via the body
field, and parse conditionally. That’s fine, if not a bit verbose. Is there a nicer way to do this?
What I’m struggling with now is memory management. I’m using std.json.parseFromValue
with an allocator to create structs of the type specific messages. I’ve placed all this in a method:
pub fn msgBody(self: *const @This(), allocator: std.mem.Allocator) !MsgBody {
switch (try self.msgType()) {
MsgType.Init => {
const parsedBody: Parsed(...) = try std.json.parseFromValue(T: InitMsg, allocator: allocator, source: self.body, options: .{});
defer parsedBody.deinit();
return .{
.Init: InitMsg = parsedBody.value,
};
},
MsgType.Echo => {...},
}
}
You can probably already spot the bug; at the end of the scope the parsed message is deallocated via defer parsedBody.deinit()
. I see two ways of dealing with this:
- copy the parsed body
- defer allocation
Copying the parsed body seemed obvious but recursively cloning structs is a pain. Maybe I’m missing a useful std lib method to do that? But it was annoying enough that I thought about the second option: delegate the deallocation to the caller by adding a deinit
method on the MsgBody
, but I don’t see how to do that short of holding parsedBody
in MsgBody
. Is there a better way? Am I thinking completely wrong about this?