Zig Serialization Followup
So, the critical piece that I forgot is that while Zig does have better memory management than C, you are still own your own when it comes to managing that memory and understanding the memory model.
As @cztomsik pointed out in my question I posted. This code doesn’t work
fn regionDetail(conn: zqlite.Conn, id: i64) !Region {
const query = "select * from Regions where RegionID=?";
if (try conn.row(query, .{id})) |row| {
defer row.deinit();
return Region{
.RegionID = row.int(0),
.RegionDescription = row.text(1),
};
}
return error.NotFound;
}
Because
The problem is that you return a struct with a pointer, which is freed when you call the row.deinit(). Therefore, what you return, is invalid already.
Obviously calling alloc.dupe
to copy the array is the correct way to fix this
issue, and allows the row to continue to be deinit()
‘d at the end of the
function call.
--- a/src/main.zig
+++ b/src/main.zig
@@ -19,11 +19,7 @@ fn regionDetail(alloc: std.mem.Allocator, conn: zqlite.Conn,
id: i64) !Region {
defer row.deinit();
return .{
.RegionID = row.int(0),
- .RegionDescription = row.text(1),
+ .RegionDescription = try alloc.dupe(u8, row.text(1)),
The downside is that now the caller has to be aware of this allocation, pass
a std.mem.Allocator
in, and then also manage deallocating.
Thankfully, tokamak
already provides an easy way get an allocator passed as an
argument to this function, via its dependency injection, and will set
everything up correctly for your functions and also handle cleaning up anything
you allocate via the allocator, at the end of the request.
This made me think a lot about how the assignment operator in different programming language have huge unspoken meanings.
In Zig, simply doing an =
for the RegionID
and the SQLite result row’s first
column never caused an issue because it’s an i64
allocated on the stack, so
setting .RegionID = row.int(0)
works because the compiler just sets aside that
memory for you and does a copy. RegionDescription
being a []const u8
means
it is memory allocated at runtime (and referenced via a pointer), so the =
implies something far different.
It’s a big reminder, since I have been working with Python for a decade where the equals operator does all the memory management for you via reference counting and you can just blithely assign stuff to other stuff and it’s only when the reference count drops to zero, where you have the memory freed and everything “Just Works”.
Overall I am still enjoying Zig, since there is a lot of power that you can wield compared to an interpreted language like Python, but I’ve obviously just blown my foot off with my first couple of steps.