And I’m considering either allocator or buffer is better for my function?
On a deeper level, I come from high-level language. Thus, using allocatoris is regarded as a heavy and unnatural thing for me. I look up in std, the two implementation coexist, while even some api provide both two choices.
So, could anybody teach me when I need to use an allocator and what is the difference between buffer and allocator?
Well, thanks for your attention. I’m using zig-sqlite pkg and its real api need a c style string. However, the path parameter may mainly comes from literal or environment variables, so I think []u8 is better for path.
The caller is responsible for getting the path, why cant the caller be responsible for ensuring it meets your api’s requirements?
Generally you should prefer allocators, they are more flexible, especially if the buffer size needed is not known by the caller (that is not the case for your situation).
The caller could pass a FixedBufferAllocator which allocates from a buffer, though it may still be slower than directly using a buffer since the interface has overhead 1) from runtime known function calls 2) it provides a higher level api that has to convert to the lower level api of the vtable functions. It is possible the overhead could be optimised away.
Thank you , it seems requiring caller to meet requirements is better in my situtaion. What’s more, this reminds me allocator is much more flexible than buffer especially when the parameter needs IO.
It’s a leaky abstraction. Zig provides slices as a natural way to model the argument.
It forces consumers to solve a puzzle how to obtain nul-terminated path.
It doesn’t matter from performance perspective. The whole domain could be opinionated and provide nice slice API instead of thin C wrapper which solves nothing.
I get that stdlib could leak details, (oh god ZON parser requiring [:0]u8) because it doesn’t know in which context it could be used. But IMHO “Software You Can Love” should go alongside with “Libraries you Can Love”.
Sadly, there is no convention to communicate that allocator is required only for fn call lifetime. So it’s always a guess work is it safe to pass a caller stack arena (for example). Especially for your case wrapper should ensure sqlite doesn’t keep references.
and this way the api still works for existing code but the allocation is gone, and people who didn’t use an allocator before with [:0]const u8 still don’t need to
and one thing slices support is enforcing termination as part of the type :3. If that detail is always required why is it bad to leak that requirement?
An easy puzzle.
but it could from a memory perspective if in a constrained environment, the caller will be able to terminate the path at least as efficiently as doing it internally if not more efficient.
We don’t know where Op is running this code. Though it is reasonable to assume they would have mentioned if it was anywhere but a standard modern pc.
a stack allocated buffer in the callers frame may indeed be okay, though that is unlikely and the caller would know it, and either use a global buffer or heap allocate.
The point is a stack buffer in init is most probably not okay.
I tend to agree that for this particular case. [:0]u8 is a better option. It’s too much hassle to provide an allocator for a mere dup when caller could make own easy abstraction.
at that point the best option is to mention how long the memory needs to be kept alive (e.g. ///path must not be freed until the returned database is deinited or ///alloc must be an arena that may only be deinited after the db is deinited, etc.) and expect api users to read documentation comments, which is reasonable imo
EDIT: note that in this case, the example i posted above with the initAlloc redirecting to init would still need to do a dupe with the allocator to preserve the lifetime constraints