It is infact pretty intresting pattern. Here is my best attempt at explaining it. Most common pattern in functions is like so
pub fn getFullNameFromJsonString(arena: Allocator, str: []const u8) []const u8 {
// do some work which uses temporal allocationes
const temp = std.json.parseFromSlice(
struct { name: []const u8, surname: []const u8 },
gpa,
str,
.{},
) catch @panic("OOM");
// compute final result
const result = std.fmt.allocPrint(arena, "{s} {s}", .{ temp.value.name, res.value.surname }) catch @panic("OOM");
// free temporary allocations (not using defer for clarity)
temp.deinit();
return result;
}
If we imagine how values are placed on arena we would get this.
Final step requieres us to free
temp
value but we can’t since its not last.
So idea is to have 2 arenas.
This way on function exit we can clear
Scratch
arena and
result
will still be returnable.
Here is more full fledged version using 2 arenas:
const std = @import("std");
const Allocator = std.mem.Allocator;
const Arena = std.heap.ArenaAllocator;
pub fn getFullNameFromJson(scratch: Allocator, result_arena: Allocator, config: []const u8) ![]const u8 {
var scratch_scope = Arena.init(scratch);
defer scratch_scope.deinit();
const res = try std.json.parseFromSliceLeaky(
struct { name: []const u8, surname: []const u8 },
scratch_scope.allocator(),
config,
.{ .ignore_unknown_fields = true },
);
return try std.fmt.allocPrint(result_arena, "{s} {s}", .{ res.name, res.surname });
}
pub fn userScoreString(scratch: Allocator, result_arena: Allocator, config: []const u8, points: usize) ![]const u8 {
var scratch_scope = Arena.init(scratch);
defer scratch_scope.deinit();
const name = try getFullNameFromJson(scratch_scope.allocator(), result_arena, config);
return try std.fmt.allocPrint(result_arena, "{s}, your score is {d}", .{ name, points });
}
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
var scratch = Arena.init(gpa.allocator());
defer scratch.deinit();
var result = Arena.init(gpa.allocator());
defer result.deinit();
std.debug.print("{s}\n", .{try userScoreString(
scratch.allocator(),
result.allocator(),
\\{
\\"name": "Andrew",
\\"surname": "Kraevskii"
\\}
\\
,
10,
)});
}
There is one problem with that version: we are not freeing name
in userScoreString
. This happens because we asked getFullNameFromJson
to allocate the value on the result arena, and we never free it. If we pass the arenas swapped, it will solve this issue.
This makes sense if we consider that, for getFullNameFromJson
, the result
value is a scratch
value for userScoreString
.
const std = @import("std");
const Allocator = std.mem.Allocator;
const Arena = std.heap.ArenaAllocator;
pub fn getFullNameFromJson(scratch: Allocator, result_arena: Allocator, config: []const u8) ![]const u8 {
var scratch_scope = Arena.init(scratch);
defer scratch_scope.deinit();
const parsed_json = try std.json.parseFromSliceLeaky(
struct { name: []const u8, surname: []const u8 },
scratch_scope.allocator(),
config,
.{ .ignore_unknown_fields = true },
);
const name_surname = try std.fmt.allocPrint(result_arena, "{s} {s}", .{ parsed_json.name, parsed_json.surname });
return name_surname;
}
pub fn userScoreString(scratch: Allocator, result_arena: Allocator, config: []const u8, points: usize) ![]const u8 {
var scratch_scope = Arena.init(scratch);
defer scratch_scope.deinit();
// swaped arenas here when passing
const name_surname = try getFullNameFromJson(result_arena, scratch_scope.allocator(), config);
const score_string = try std.fmt.allocPrint(result_arena, "{s}, your score is {d}", .{ name_surname, points });
return score_string;
}
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
var scratch = Arena.init(gpa.allocator());
defer scratch.deinit();
var result = Arena.init(gpa.allocator());
defer result.deinit();
std.debug.print("{s}\n", .{try userScoreString(
scratch.allocator(),
result.allocator(),
\\{
\\"name": "Andrew",
\\"surname": "Kraevskii"
\\}
\\
,
10,
)});
}
It will look kinda like this. We can see how some values leave as both scratch and result (they cross function boundry).