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 {
...
}
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,
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 });
}
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.
Top comments (0)