Well in terms of convenience, the main problems are not the datastructures themselves, but the places you use them in. Let’s say you have a data structure that contains a list:
const MyStruct = struct {
list: ArrayList(u32),
pub fn init(alloc: Allocator) void {...}
...
};
Then either you need to make the entire struct generic, which also propagates the problem one layer up:
pub fn MyStruct(comptime AllocatorType: type) type {
return struct {
list: ArrayList(AllocatorType, u32),
pub fn init(alloc: AllocatorType) void {...}
...
};
}
Or you need to hardcode the allocator type:
const MyStruct = struct {
list: ArrayList(GeneralPurposeAllocator(.{}), u32),
pub fn init(alloc: GeneralPurposeAllocator(.{})) void {...}
...
};
This is the convenient choice, but makes it more difficult to change the allocator.
And this includes even changing the config of the allocator. Like imagine you found a memory leak, but the stack trace was too short and yout want to momentarily increase the number of stack traces captured by the GeneralPurposeAllocator. Then you need to go into 100 different files and change the allocator type to GeneralPurposeAllocator(.{.stack_trace_frames = 15})
As for the cost, it’s difficult to estimate the actual impact. If you mostly use one allocator for everything, then it will be negligible.
But if you use many different allocators and arenas(note that the arena type would also be generic over its child allocator type), then the amount of generated code might easily explode.