I have been using Zig for about 2 months. And I wrote C, C++, Java, PHP, Python, Go, Rust, Lua… before this language. So Zig isn’t my first language. I have some suggestions/[questions].
Everybody implements their VTable and interface system like language designer in Zig. At least I have seen these 5 times in different projects.
Why doesn’t Author add this feature (interface) to language? I think something like Go interface is enough for everyone (already everybody try to do this). I think this feature missing like “linked list” (or vector) implementations in c, “string operations” in cpp … And I read Reader, Writer implementation in std library. writer(), any() like functions bind together with chewing gum. They deserve better!
A lot of mess on package system.
ArrayList, AutoHashMapArray… stay on std namespace but they sits on some package like std.containers.* or std.list., std.array. … why this Structs are so special than Allocators or others?
Some functions’ packages are unpredictable, for example Allocater in td.mem.Allocator, but all Allocators are in std.heap, and std.mem generally looks like c language string header (byte array operations). I don’t know problem is about naming or hierarchy
Maybe these are not problem, which are features. Maybe someone says it is open source project, you can contribute. But i couldn’t change package names or syntax. But i can say that its really ugly for newcomers.
*** I couldnt wrote everything, for example; like "closure"s, which can make with a little efford and i understand Author tries to keep Language as simple as possible (thanks for God). What do you think about these suggestion / questions?
I think the fundemental problem of interfaces is that they tend to be over-used which results in code that is harder to read and harder to argue about (you do not know all the possible implementations and it’s more difficult to argue about the flow of the code). If the interface is more difficult to write, then it makes you think about the alternatives, and most of the time an interface actually is not the right solution. A tagged union for example is a great alternative to interfaces, and the only reason we don’t see them as much in other languages is because interfaces are just more convenient.
Of course there are problems where you need runtime dynamic dispatch, like e.g. allocators. However in these rare cases it is still easy enough to create the VTable with a few wrapper functions yourself. Overall I usually need just 5 lines per function, 1 line in the vtable, 3 lines for the wrapper and 1 line for the VTable initialization.
Furthermore not being a language feature removes some of the magic, and allows you to do things that often are impossible with interfaces. For example you can put other stuff in the VTable, like I sometimes put other type-specific constants like an ID in there, in other languages these would need to be defined as a function that always returns the same value.
I think the linked list comparison is quite accurate: 90% of the time it’s the wrong choice, and in the few cases where it isn’t, it is easy enough to implement yourself.
In my opinion a language should make it easier to make the right choices in the average use-case, instead of being convenient to use in every possible use-case.
Welcome to a pre-1.0 language. I think we’ll see a whole redesign of the standard library shortly before 1.0. For now though I think it works well enough, and I’d prefer if things don’t get shuffled around each release, only to discover a few years later, that maybe Allocator does make more sense in std.mem after all.
While most people use it for bytes, it can be used for any slices.
Tbh, in all my decades of programming I can count the situations where I actually needed a C++ like vtable on one, at most two hands (in my OOP days I definitely used C++ virtual methods much more than was actually necessary though).
Most of the time a simple struct with function pointers is just fine to inject user code into a library.
I have used it for a shorter time, so my impression may not be so convincing.
For question 1, when I actually started to use the polymorphism I implemented, I noticed that there are so many possible implementations. In the past, when I used C language, I was used to using one of the polymorphic implementations (many times it was because C could not easily implement many solutions, such as tagged union polymorphism, which I almost did not consider because it was too cumbersome.), so when learning to use zig, I noticed that there are so many polymorphic implementation memory layout possibilities. These implementations have different advantages. Some have higher performance after optimization, some have more flexible dynamic change features, and some have better grammatical decoupling and are more suitable for engineering development. It is difficult for me to say which one is the best.
Regarding question 2, I noticed that the standard library of this language is undergoing drastic changes, and I believe it will continue to change radically.
My sense is that, as the language itself stabilizes, the standard library will become less stable, since there will be more attention on it from the core team (see this talk for some evidence of this). In the end, this issue looms over everything currently in std:
Tagged unions are great for polymorphism. Interfaces are great for contracts. Contracts are great for libraries and runtime dynamic dlls (plugins). Just because someone misused interfaces in a language that didn’t have tagged unions shouldn’t prevent us from having good contracts.
The dream interface implementation in zig would actually be optimized similar to anytype when known and only use vtable as fallback.
This is my sentiment, too. I would be perfectly content with even just some thin syntactic sugar over anytype. As many times as I see it repeated, I just can’t accept “you must read the docs to know what can be passed to anytype”. I find this to be a lazy argument. Of course we should read the docs and understand the functions we are calling, but this practice is not a suitable replacement for simply being able express intent directly in code at the call-site. We don’t use anytype everywhere because we understand that there is value in having strongly-typed parameters.
Sure, I can surmise that when I see a writer: anytype parameter that it probably needs a type that implements the same write function we have seen a thousand times before, and the compiler will complain if I try to built without this requirement, but it envision something akin to this pseudo-code:
…and then writer: Writer as the parameter. The compiler can treat this exactly like an anytype, but it could additionally provide the following:
Communicates intent precisely. anytype has all the sexiness of a void* in C, dynamic/object in C#, or interface{} in Go: it gets the job done, but never feels nice to use.
Reduces the amount one must remember. This pretty much ties back to the #1: it tells you right there what it needs without even looking.
Permit tooling to provide better diagnostics. If I pass in a reader, an LSP can put big red text right there saying this is the wrong type, and we don’t need to pick it out of an error log later after a failed compilation. Perhaps someday the compiler will be able to do this anyway, but right now it can’t.
For my personal needs, it is not completely necessary to provide interface constraints on function signatures in the language. However, it is helpful for a standard library to take the lead in implementing the interface constraints based on compile-time assertions at the beginning of functions. The coding standards implemented by the standard library can help reach some consensus and reduce the communication cost between the library and developers.
Yea I think the library will need some major restructuring and renaming before 1.0 but we can avoid shaking the tree too much for now so most people don’t need to rewrite / fix code too much at the moment. For example a few years ago I voted in favor of renaming all of the stdlib to using snake case as the naming convention (vs keeping it as camel case). This is subjective but the point is that if this were to get adopted, it would literally break everyone’s code lol. So this would have to be a near 1.0 change.