result[0] points to freed memory.
Because joined is freed by defer allocator.free(joined); when get function ends, and a reference to joined is appended at result.
If the result is a slice that contains dynamically allocated pointers, those have to be individually freed. There’s no real way around that, unless you do know those have been allocated inside an arena.
Much like you have an allocation function (get()) you can have a deallocation function (destroy() or similar) to which you can pass slice and allocator to do the deallocation in one step. This makes it easier for the caller to manage memory correctly:
const x = get(allocator, ...);
defer destroy(allocator, x);
I think what you’re looking for is either to accept arena, and “lift” the whole constraint to the upper level, or return a struct with a newly created arena, and in that case, you’d probably dupe() the input first.
Arenas are often used to denote how long certain things will “live” together.