I distinctly remember one of my first "real" jobs, somebody had written a C# library for GraphQL. It had some sort of bug or lack of functionality I had to fix. The author had fallen hook, line, and sinker for sealed classes and all that nonsense, so the shortest path to using the rest of their code was to fork the library and re-write the thousand offending lines and all references thereto. Luckily it wasn't a deep dependency.
Contrast that with similar issues in typical Python. The library likely accepts my favourite duck-typed solution, if it doesn't then I can likely work around it with an appropriate choice of dunder methods in the code I have interacting with the library, and if it still doesn't yield then I can just rewrite (monkey-patch) the offending code at runtime.
Not that either of those scenarios are objectively optimal, but they illustrate one of the principles people have in mind when they use an interface like allocators (not the only one, I'm sure other commentors will talk your ear off), that when push comes to shove you'd prefer to be able to use your existing code to solve your new problems. And that does empirically mostly work here; you can use nearly the entire Zig stdlib (especially when ignoring things like the posix namespace) and most of the rest of the ecosystem as well on embedded devices, in WASM, in bounded-memory applications, in generic application code, in fast-running CLI apps not even bothering to free, etc.
I agree with much of your discussion about arenas by the way. They're a moderately poor fit for the current interface in Zig, largely because the free functionality is questionable at best. IME, most uses of arenas actually know they need such a thing. Functions taking allocator arguments and expecting the allocator to be an arena should take an arena argument. When using arenas for lifetime management, something should probably own that arena rather than implicitly assuming whatever gets passed through the stack is correct. Passing arenas to arbitrary functions expecting general-purpose allocators is prone to leaks, and you'd be better off using the arena (or a buffer created therefrom) as a backing allocator for a data structure capable of actually freeing if you can't trust the implementation you're calling into (and if you can trust it, they should expose a better API allowing you to detect that trustability at the callsite).
[–] sethops1 1 day ago
[–] hansvm 1 day ago
The bigger design point is who's responsible for knowing where the object lives. Zig has a balance of power more in favour of the caller than the callee, a point I tend to agree with since it's quite easy for a caller to know exactly what they want done and quite hard for a callee to know what all possible callers might want done. That principle is applied pretty liberally through the rest of the language.
[–] xedrac 1 day ago
[–] hansvm 1 day ago
Jokes aside, that's not the worst idea (you often see the stdlib do something in a similar vein with managed vs unmanaged data structures), but I bet it doesn't survive contact with real-world programmers well enough to actually provide the portability that normal Zig code has. For transitive "style" impositions, it only takes a low rate of non-compliance for the perceived benefit to disappear.
If that's a goal you have (and IME most code is lightning fast to write, with all the heavy lifting going to impedence mismatches like the one being described, so I tend to be happy to write a bit of extra boilerplate (I like Zig, but it's an absurdly verbose language) if it means the hairier integration points will go more smoothly, and I therefore do have that goal) then the allocator API empirically works, the other solutions I've seen work much less, and I'd want to have a pretty good idea of why a contender would sate my needs instead.
If that's not a goal you have, you do you man. I can shovel out sealed Scala classes or unmaintainable numpy with the best of them as the situation demands. Nobody's created "the best" language yet. We're just casually discussing tradeoffs.