Makes sense since Zig is meant to be a replacement for C and "C is NOT a modular language! The best way to use C is to roll your own (or copy and paste) data structures for your problem, not try to use canned ones!" from a comment here: https://news.ycombinator.com/item?id=33130533
Edit: I call this misfeature of the C language the "polluted/busy call site syndrome".
The current build system and type system go a long way to encourage library use (since it's quite easy) and the future package manager will be yet another step towards that.
I have zero experience with Zig but this contradicts what the other poster in this thread said. Not sure what to make of this.
In service of this, even in Zig's current pre-1.0 state, adding a library can be as simple as something like the following in your project's build.zig:
// Argument Parsing Library
exe.addPackagePath("clap", "lib/zig-clap/clap.zig");
This and the language just having generics (which isn't necessarily the goal of all c-replacement languages i recently found out) suggests to me that the language as it currently stands encourages libraries to be written and reused.In Zig, allocators are "just another argument", functionally an interface so as a library author you have to pay less attention to whether your library can be used in hostile environments. I'm quite sure this idiom exists primarily to just make Zig libraries (like the stdlib) useful in more places.
Certainly, Zig doesn't have all the tools you'd expect in other languages to aid library authors and consumers. I personally would love to see proper interfaces in the language, rather than the interface-by-convention situation we have right now. It's a matter of tradeoffs, many of which I imagine will be addressed and reconsidered as the language matures.
AFAIK there isn't any dedicated pretty syntax, but there are plenty of solutions that aren't too painful. They all rely on the fact that you're not generating a "new" struct with each call -- comptime function calls assume that the same inputs will yield the same outputs and cache the results to enforce that behavior, so if you call Writer(foo) in two different places you get the _exact_ same struct in both places.
One option would be to instantiate the Writer type just as you would dependent generics in any other language.
fn foo(comptime writerFn: anytype, comptime WriterT: Writer(writerFn), writer: WriterT)
Another option would be to just check the type at comptime. You're able to throw custom error messages during compilation, and comptime code doesn't add runtime overhead of any sort.
fn foo(writer: anytype) !void { checkWriterT(@TypeOf(writer)) }
For completeness, it's worth noting that the correct behavior for strings and more complicated objects is still AFAIK a subject of debate with respect to comptime caching. It should have some nice solution by 1.0 in a few years, but for primitives like functions and types you should have no issues with either of the two above approaches.
I think some of this can be achieved in the function body by checking the incoming generic types via Zig's comptime type inspection features (see: https://ziglang.org/documentation/master/#typeInfo). Kinda like 'comptime type-asserts'. Together with @compileError this would at least provide more useful error messages if a generic type doesn't have the expected features.
PS: I think it's important to not look at Zig's generic feature in isolation, instead it goes hand in hand with comptime and reflection.
https://www.godbolt.org/z/xe98Md74r
E.g. if you change the method 'bla' to something else:
https://www.godbolt.org/z/77118zG1K
...there's probably more advanced helper functions in the same std.meta.trait module, I haven't explored that yet.
Also, disclaimer: I haven't dabbled much yet with Zig generics, don't know if this is the proper style (in this case it's probably better to get rid of T entirely and just use anytype for the obj argument).
One concrete way this stood out to me is that I don't think Zig has any way to say "this function should take a Writer". (I.e. a File or a Socket or something you can stream bytes into.) Zig does have a Writer abstraction in the standard library, which takes a basic write function and provides lots of helper methods like writeAll. However, the Writer abstraction gives you a new struct type, and while you could write a function that takes a specific type of Writer, I don't think there's any way to say "any Writer". Instead you usually have to take "anytype".
I think things like the Writer interface in Go or the Write trait in Rust are very powerful and useful for organizing an ecosystem of libraries that all want to interoperate. This might be something Zig struggles with in the future. On the other hand, a lot of Zig's design seems targeted as use cases where you don't necessarily want to call lots of library code (which for example might be allocating who-knows-how-much memory from who-knows-where). In use cases like kernel modules and firmware, there might be an argument that making lots of fancy abstractions isn't the best way to go about things. I'd love to hear more about this from more knowledgeable Zig folks.