><i>Zig is not a mature language. But it has made enough useful choices for a number of companies to invest in it and run it in production. The useful choices make Zig worth talking about.Go and Rust are mature languages. But they have both made questionable choices that seem worth talking about.</i><p>Well, Zig's native string type support, or lack thereof, is also a questionable choice. It's not like Zig did all the right choices and only Go and Rust made questionable ones.
Nim is also a strong player as a systems programming language. In terms of memory management, it's configurable, and by default you get ARC (no GC). I've written a hobby kernel (if you can call it that) in Nim[1] as well as Zig[2], and I found Nim to be much more ergonomic and approachable. The fact that Zig requires weaving an allocator through most calls that may allocate gets in the way of what I'm trying to do. I'd rather focus on core logic and have ref counting take care of dropping memory when appropriate.<p>One thing I wish Nim had though is true sum types with payloads. I think there's an RFC for that, but it's a shame it's not in the language yet.<p>[1] <a href="https://github.com/khaledh/axiom">https://github.com/khaledh/axiom</a><p>[2] <a href="https://github.com/khaledh/axiom-zig">https://github.com/khaledh/axiom-zig</a>
> People have been making jokes about node_modules for a decade now, but this problem is just as bad in Rust codebases I've seen<p>I agree... but I think this is more indicative of a cultural problem than a language design/standard library scoping problem. A compact standard library is more resistant to scope creep. I don't want every language to end up like C++, and I think keeping the scope of the standard library small helps avoid that. On the other hand, popular third-party libraries that provide common functions have too many third-(fourth-?)party dependencies. Keeping dependency trees small should be a priority for such tools, but convenience trumps all right now so it isn't valued as it should be.
Just a correction: most std C functions don’t allocate. strdup does but it was only recently adopted into the standard, it was previously an extension.<p>Similarly zig’s stdlib shouldn’t allocate behind your back, except for thread spawn where it does:
<a href="https://github.com/ziglang/zig/blob/5cd7fef17faa2a40c8da23f0ef2485df0af39ed4/lib/std/Thread.zig#L666">https://github.com/ziglang/zig/blob/5cd7fef17faa2a40c8da23f0...</a><p>Generally speaking, it’s as mentioned just a convention. A zig library might not allow its users to pass allocators for example.<p>In C++, stl containers can take an allocator as a template parameter. Recent C++ versions also provide several polymorphic allocators in the stdlib. You can also override the global allocator or a specific class’ allocator (override placement new).
WRT the standard library. There are tradeoffs. Yes, a big standard library that has everything you need is great, as long as it is well designed, well maintained, and has some mechanism to prevent the whole thing bloating every executable.<p>But executing well on that is difficult for a number of reasons:<p>- the release cycle of the standard library is (usually) tied to the compuler, which often means it can't evolve quickly.<p>- backwards compatibility is a much bigger deal for the standard library. Which means if you have a big library you will eventually have a big pile of deprecated APIs you will probably never be able to actually remove. It also feeds into the next point
- new development in stdlib can be hindered by the need to get it right the first time.
- the creators of the language probably aren't experts in every area. Which means in order to include say, a good compression library you either need to recruit someone who is an expert to write and maintain the package in your stdlib, or link to a library in another language, and the latter is still really an external depency.
- a large stdlib is a large maintenance burden<p>Python has a large standard library, but it has parts that are deprecated and/or largely abandoned, including packages for technologies that are no longer widely used. And its http packages are rarely used directly because third party packages like requests or urllib3 are better.<p>Go's standard library that is praised here isn't perfect either. The log and flag packages are often insufficient, and the implementation of net.IP is suboptimal [1].<p>I think probably the best balance is to start with a fairly small standard library, and when community libraries for key functionality become popular and stable, then pull them in to the standard library or otherwise make them official. And maybe have official libraries that are external to the stdlib and maybe have less rigid backwards compatibility garantees that can move faster than the language itself.<p>[1]: <a href="https://tailscale.com/blog/netaddr-new-ip-type-for-go" rel="nofollow">https://tailscale.com/blog/netaddr-new-ip-type-for-go</a>
> People have been making jokes about node_modules for a decade now, but this problem is just as bad in Rust codebases I've seen.<p>Just w.r.t the issue of size (not of scale) - cargo caches sources in ~/.cargo so they're not shared by every project on the system. Additionally, rustc uses dead code elimination by design so there's not nearly as bad an issue as in JS where it's not possible to tree shake out every unused class or method.<p>Most of the bloat of target directories are stale incremental build artifacts, because cargo does not do any automatic cleanup.
There's also Odin[0] too. I experimented them all and Odin is pretty nice. Nim is also good too but a lot more features.<p>But - I concluded that language matters a lot less compared to APIs. Yes, the language should have enough good features to let the programmers express themselves, but overall well designed APIs matter a lot more than language. For example -tossing most of the C stdlib and following a consistent coding style (similar to one described here -[1]), with using Arenas for memory allocation, I can be just as productive in C.<p>[0] - <a href="https://odin-lang.org" rel="nofollow">https://odin-lang.org</a>
[1] - <a href="https://nullprogram.com/blog/2023/10/08/" rel="nofollow">https://nullprogram.com/blog/2023/10/08/</a>
> Similarly, if you go looking for compression support in Rust, there's none in the standard library.<p>I have no idea why that is considered a criticism.<p>Compression support is the kind of thing which is going to evolve over time and compression formats will change and performance and APIs of different libraries will get better.<p>You avoid the problem where the compression library in the std library is the one that experienced programmers tell all the noobs that they should never use.
>Zig is practically alone in that if you write the next() method and and don't pass an allocator to any method in the next() body, nothing in that next() method will allocate.<p>It's not quite as simple as that. This can allocate just fine:<p><pre><code> fn next(self: *Self) {
self.array_list.append(5);
}
</code></pre>
... where `array_list` is of type `std.ArrayList(u32)`. `std.ArrayList(T)` takes an allocator at construction time (`array_list = std.ArrayList(u32).init(allocator);`) and stores it as a field, so its methods don't need to take an allocator parameter.<p>(And yes, there is a version of `std.ArrayList(T)` called `std.ArrayListUnmanaged(T)` that does *not* take an allocator in its constructor and does take an allocator in all its methods that need to allocate.)
> 3 years? 68 lines of code. Is it not safer at this point to vendor that code?<p>Why, though? There is no bitrot or build flakiness with unnecessary changes if no changes happen, so what's the actual argument for this? Why is it missing from the article?