Writing abstract interfaces in zig is an absolute nightmare!
Being a bit bored and having a some extra time during Christmas motivated me to learn zig. As usual, when learning a new language, you experiment a bit, write some small test programs to discover the syntax and idiomatic ways to solve problems and learn the standard library. One of the fundamental things that you’ll eventually find the need for, sooner or later, is defining abstractions and interfaces, and I was a bit shocked to discover that zig simply doesn’t support that!
What do I mean?
I’m talking about an equivalent of pure abstract classes in C++. Something like:
|
|
Before I get to zig, let’s think about how we could implement this in C? There’s a very good paper describing the details of how to approach object oriented programming in C. The gist is to have an indirection:
|
|
This is the basics of dynamic dispatch. Of course, I’ve ommitted a lot of
important details i.e. having the interface definition opaque, passing pointer
to the object to methods etc - it doesn’t really matter here. Additionally,
the downside of this simplified approach is that if you create multiple instances
implementing Calc
then the function pointers will be duplicated multiple
times across them so, we usually go a step further and define a vtable
:
|
|
In general, this is all that C++ is hiding from you and doing behind the scenes to make our lives simpler and focus on the code rather than the technicalities.
The client code can use Calc
as an abstract interface (and its associated
functions e.g. calc_sum
) without any knowledge about how and where it
is implemented.
Now, C being C, requires that you implement all of that manually since there are no language constructs to support that. Some projects do that. One prominent example I can think of from the top of my head would be DirectFB. DirectFB defines interfaces in that way for majority of its primitives here’s one example.
How does that relate to zig?
I only spent a couple of weeks with the language but so far I don’t see any language support for abstract interfaces. In fact, looking at how standard library implements allocators, I’m convinced there’s none! Let’s have a look together shall we?
As an example, let’s look at concat.
This is the function signature:
|
|
It takes an Allocator so, what is an Allocator?
It’s just a struct
with two pointers:
|
|
VTable in this case defines a set of function pointers that Allocator implementations have to populate. That sounds familiar doesn’t it?
Let’s have a look on how allocators are implemented. I’ve picked GeneralPurposeAllocator completely arbitrarily. The interesting bit is here:
|
|
This populates the Allocator
structure with its own vtable and a pointer to its own instance. Case closed.
Why does this suck?
Because it’s completely manual!!! Surely your language is lacking if it doesn’t support such fundamental programming concept like abstract interfaces. In fact, I’d be okay with lack of support for this if there was any other, equivalent idiomatic construct given in the language but there isn’t.
Now, you might say that it’s not idiomatic to do things like that in zig but zig does it itself in its own standard library!
Another, more serious, reason why this suck is that again you have to rely on explicit type casts in the code implementing the interfaces. In other words, there’s completely no type safety guarantees! Here’s an example of what I mean:
Let’s have a look on one of Allocator’s interface functions - alloc:
|
|
Now, this is the implementation from GeneralPurposeAllocator:
|
|
You see the problem? We’re operating on anyopaque
and are forced to
explicitly cast to our own type inside the implementation. This is no better
than C! This is exactly the same as:
|
|
False advertisement
Maybe it’s just me or maybe it’s the hype around the language which is being sold by big Youtubers, like The Primeagen, as the next big thing. The lightweight alternative to rust! The perfect new language that’s gonna take over everything by storm. The most enjoyable new language to write in. So on and so forth…
I mean, it builds expectations which inevitably will lead to disappointment.
The language is okay but it’s definitely not mature enough to be placed in the same category as e.g. Rust or any other well established systems programming language.
Since I mentioned Rust, Rust supports building abstractions through e.g. traits that are closer in nature to a classic inheritance model we all know from e.g. C++.
zig in its current form is probably closer in nature to nim although, ironically, nim does seem to support inheritance and dynamic dispatch.
Conclusion
Learning a new programming language always brings in a new valuable insight. It’s interesting to see how different languages and tooling around them solve similar problems. Despite initial let down I’m gonna continue learning zig and plan to use it in practical application as well.