It is a good default for lib code because it makes that code truly re-usable. The same is true for great C libraries, they let you provide a block of memory instead of calling malloc inside. The library should not get to decide if/when allocations should be done.
In Zig app code you don't have to pass allocators all over if you don't want to, that's your code so do what you want. But for libraries it's the right thing to do because it puts the caller in control.
The reason you have to pass allocators instead of making this implicit like in e.g. Odin, is because Zig just takes a firm stance against implicit logic, firmer than any other language I've ever used. The end result is that people who like Zig tend to like passing allocators around even in their own code.
The library developer doesn't know what allocator will be passed, and the library user doesn't necessarily know how it will be used. They share an interface, so it will [probably] work, but it might not behave (mem/perf wise) as either expects. ArenaAllocators in particular don't play great with ArrayLists or nested ArenaAllocators.
I don't think it's a huge problem, but it's more subtle than an explicit allocator is a better because it provides more flexibility.
> Zig just takes a firm stance against implicit logic
I bought that as Zig's M.O., but then came the automatic "async/IO" colorless functions which is a huge chunk of implicit and hidden logic.
That's more implicit logic than what's in say JavaScript async AFAICT. Arbitrary int sizes with implicit casting rules, etc. More recently was the attempts of memory safety via an some sort of (optional) implicit GC mode. Personally I don't get it.
Are you sure you're up to date?
The async design changed multiple times, and the current approach is that of requiring passing an I/O interface that encapsulates the logic, same approach as for passing around an allocator.
This effectively makes all I/O bound code "quasi-async", and it's all explicit and the caller is in control.
As far as I understand it, the idea is that the IO interface is "magic" in the sense that it affects which version of a function gets called: either the synchronous, linear version, or the asynchronous, stackless, suspensible state machine version.
This has a lot of implications, especially for function pointers and other forms of dynamic dispatch.
My personal take is that async code is fundamentally different from sync code, because the weights of various tradeoffs change dramatically. In my view, "colorless" functions are not really a worthwhile goal in the first place. But I've been wrong before, so it may turn out to be great, we'll see.
There is no magic about the I/O interface. It's a regular dynamic dispatch, the same as if you use virtual functions in C++. Currently, and in the foreseeable future, there are only two options how it can run, blocking mode where each task occupies a dedicated thread, or using stackful coroutines. The interface implements blocking functions, there is no async waiting anywhere in there. But because it's dynamic dispatch, it allows the async implementation to do the "prepare operation, suspend until completed, wake up" dance, where the threaded implementation would just run the blocking syscall directly. There are plans to add stackless coroutines, but that will require many language restrictions to happen first, so it's not going to happen any time soon and when it does, the interface will likely change to accomodate it.