Cover image for Cool Zig Patterns - Configuration Parameters
Felix "xq" Queißner
Felix "xq" Queißner

Posted on

Cool Zig Patterns - Configuration Parameters

While working on and with antiphony i required a way for the user to allocate transient memory for dynamically sized return values.

But the API for rpc functions looks like this:

// raw:
fn toString(integer: u32) error{OutOfMemory}![]const u8 {
// or with self:
fn toString(host: *Host, integer: u32) error{OutOfMemory}![]const u8 {
Enter fullscreen mode Exit fullscreen mode

You see, there's no allocator involved here. One solution might be adding more parameters and deciding on the number of parameters what to do, but as the definition of the RPC call is defined as

.toString = fn(u32) error{OutOfMemory}![]const u8,
Enter fullscreen mode Exit fullscreen mode

so our second variant with self already deviates from the definition by one argument.

My solution to this was a Configuration Parameter. As the first parameter is kind of magic already, we can add some wrappers:

fn AllocatingCall(comptime T: type) type {
  return struct {
    value: T,
    allocator: std.mem.Allocator,

fn toString(wrap: AllocatingCall(*Host), integer: u32) error{OutOfMemory}![]const u8 {
  const host = wrap.value;
  _ = host;
  return try std.fmt.allocPrint(wrap.allocator, "{d}", .{ integer });
Enter fullscreen mode Exit fullscreen mode

With this, we can put the configuration of the function call into the first parameter, and allow several configurations with different wrapping functions.

In the backend, we can just check if the first argument is of type AllocatingCall(*Host) or *Host. If we have a AllocatingCall(*Host), we can just create an ArenaAllocator and pass that to the wrap parameter. With this, we can return transient memory to the caller easily without having to manage memory in a complex way.

Discussion (0)