I’ve been translating some C code, and it makes heavy use of a data design pattern which would be a good addition to Zig.
The way it works is, say you have a Frob
type, and that has a pointer to a char *
“array”, zero terminated: a string, basically.
So you malloc sizeof Frob + n where n is your chars, all at once, and cast it to a *Frob
. Then you populate the Frob, and you get at the byte array next, with (char *)frob[1]
. That works because C is crazy.
The point is that you need two blocks of data, which will be traveling around together, and you get them back as one contiguous chunk. Since malloc
in C has absolutely no idea what a type is, you just give free
the Frob pointer and it junks your string as well.
Now, Zig has an actual type system, so we can’t quite do it that way. Which is where we’re suffering for it: you’d have to request raw bytes, cast regions of them into discrete pointers, then get rid of all of it: and the allocator is going to want the whole thing gone at once, which would be… painful. You can do it, yes (maybe?), but what you’re doing is clearly illegal: you’d cast the lead pointer to a byte slice, then count your slice at the end and add that to the byte slice’s length, then hand it all off to free
. It should work but I don’t like it, and it’s not clear that all allocators are even going to put up with that. Violates the contract is what I’m saying.
What I want is to be able to do this:
const frob, const frobname = try allocator.createMany(.{Frob, [14]u8});
frob.kind = .fraggle;
frob.name = frobname;
@memcpy(frob.name, buf[0..14]);
// Then later:
try allocator.destroyMany(.{frob, frob.name});
// That could be disguised within
// frob.destroy(allocator);
Where the contract specifies that any bytes given to a destroyMany
must come from a call to createMany
of the same tuple type. So the allocator can just add up all the memory and junk it.
Note that create
can’t really do this, it would return a pointer to an anonymous tuple, when what you want is two pointers, one skinny one fat. You can dereference that, assign const frob = &retval.@"0";
, but it’s ugly stuff, and that’s before you try and get rid of it. I’d rather use an API.
It’s a good technique, minimizes allocations and fragmentation, keeps data together. We should be able to do that in Zig, and it should be easier and cleaner than the C equivalent.