The Zig team makes the language exactly as I would want, even when they don't

Back in 2019, I switched to C (from C++, with experience in many languages). I did this in order to get better at computer science, and gain knowledge that would apply to my programming, whichever languages I ended up using in future. Over the years since then, I’ve run into various problems with C, and found my preferred ways of programming. Things like tagged unions, fat pointers, error handling, etc.

I’ve also had an interest in Zig during that time, and I’ve occasionally taken a look to see how Zig handles the things I find painful in C. Consistently, the Zig team has already solved the problem in exactly the way I would want, or in an even better way than I had thought of! However, there was one decision I didn’t like: removing @usingnamespace. As a game programmer, I often want to have the same types and basic functions available in the global namespace across many files. I can accept one wart on an otherwise brilliant language, but in my dissatisfaction, I decided to read through the git issue regarding this removal.

And, I must admit, the Zig team was right!

I read through the reasoning around the issue, and while the functionality of @usingnamespace was nice, I don’t think it’s worth having invisible declarations - that is, declarations present in the file’s namespace, with no hints about where they’re from. Let’s say the “bob.zig” namespace has a “jim” declaration. When you open bob.zig and CTRL+F “jim”, nothing comes up. Now you have to check every instance of @usingnamespace, which could be arbitrarily many files deep!

With the cost being greater than the benefit, I think a jankier solution to this problem is preferable. Either:

  • Just make a short namespace for these things.
  • Copy-paste the declarations to the bottom of the file, possibly with a nice big comment at the beginning and end to make it easy to search for and replace when the source declarations change.

All this is to say: even when the Zig team makes what I think is the wrong decision, they’re probably making the right decision.

Thanks Zig team!

PS: Sorry for the odd choice of category; I didn’t see one that fit better.

17 Likes

That’s nice, thank you. But don’t forget the Zig team can also be wrong and you should also think for yourself and challenge our opinions and reasoning.

37 Likes

My programming-language journey mirrors your own (just add Matlab to the front of the queue), and I could not agree more, including on the usingnamespace issue. Whenever I take the time to think about any particular issue related to error handling, type inference, interfaces, etc., then read the discussions that led Andrew and the Zig team to their particular solution, I find zero complaints with their decisions. This is why the (currently) only recurring donation I make is to the zig software foundation - they’re doing more for the field of software engineering than any other organization that I’m aware of. Keep up the good work @andrewrk :sign_of_the_horns:

5 Likes

Not to worry Andrew - I won’t become a sycophant! Once I’ve made a project with reasonable size and complexity in Zig, I’ll be sure to share my (polite) criticisms. Most of the criticisms I would so far offer already have planned solutions!

4 Likes

I feel like the way the language is growing has some major synergies with my main Zig project, my graphics engine/framework. For instance, just as my build.zig and the project’s modules had started to grow in complexity so much that I wasn’t fully satisfied with @cImport, they nudged me to switch to an explicit translateC module. And as I’ve been slightly unsatisfied with 0.16 zig build --watch, because it doesn’t pick up my new shaders, because it doesn’t rerun build.zig and re-parse my render.zon, I’ve read the devlog and seen that an upcoming release might improve this too!

It is genuinely so fun to figure out the language and the tooling in real time as it’s maturing. In my 10 years of developing random demoscene codebases, mostly in C++, C and Rust, nothing has ever come close to what I’ve been able to accomplish with Zig recently.

5 Likes

I can’t recall who it was that said a language may create friction on certain usage to nudge the coders into a specific paradigm. Zig is my favorite language in that it promotes the programming style I really like, in such a natural way that you feel friction if you don’t do it that way.

I don’t have a clear list yet, but I can jot something down about the most obvious design philosophy of making dependencies explicit, as the OP also fell in love with.

Context switch is hard, both for the machine and the human. In a typical non-Zig program, your app talks back and forth with different objects and concerns. You have to trace through the call chain to understand what is happening. And anything could happen in between, with all sorts of possible exceptions.

In Zig, this style gets frustrating faster than any other language. If you allocate memory here and there, if you litter IO here and there, your code gets stinky, and the allocator and io buddies piggyback around on almost all your functions. It’s similar to the so-called colored function problem on other forums.

I don’t think it’s a problem. I think it’s a hint and a good friction. I promote a Sandwich Pattern. Instead of doing things just in time, you schedule in your mind. You think of what you will use later, and fetch them in one bulk stage. You think of what you can put off, and buffer those calls until you definitely need them. The end result is a clear prepare-grudge-commit workflow, and each stage takes a neat handful of dependencies.

For example, an array list of managed objects requires complicated cleanup:

defer {
    for (my_list) |x| gpa.free(x);
    my_list.deinit(gpa);
}

The need for the defer is eliminated if you used an arena.

You may ask: what if my resource has to be closed, let’s say, a list of File handles?

Use pools. Putting homogenous resources together.

There are many more opportunities. I won’t show off too many cases. There are many more smart people around, but I do think, that your smartness only comes into play when you see a problem. Zig presents that problem in a good way, and I sincerely hope that it insists on that philosophy.

For example, I had a use case that had to wiggle with three buffers, with the last one getting reused in a loop.

My first version had three defer’s, naively.

Then, I realized I could allocate all of them in an amalgamate buffer, and call resize if I need to.

The end result is shorter, cleaner code, with less intrusion from memory management.

I don’t hide the complexity of memory management. I push it physically away into another block of code. I loved reading such code, and I assume machines also like to.

This is not premature optimization. This is something that grows in me as a default.

(PS: AI slop has made me hairy. Every time I say “it is not… it is” I feel I’m talking like an LLM :rofl:)

The pattern is not news to experienced systems programmers, and often seen in well optimized C code, but it is easier to write spaghetti in C/C++. In Zig, it’s grinding to write spaghetti. And, you know,

The three great virtues of a programmer: laziness, impatience, and hubris. — Larry Wall

A lazy programmer then falls naturally for the Sandwich Pattern.

It is extremely frustrating to optimize non-sandwich code, too. I didn’t realize how much it hurts until getting bitten in my career work. My main programming language is Python. I always thought: hey, this is job, and who cares about performance in Python? But it bit when when I had to optimize for GIL, IO, and memory usage. Yes, you heard that. OOM is my daily cuisine as a data engineer. So I adopted the same idea of staged processing. I push code into a sandwich structure, getting as much as I can on the entrance IO, doing as much as I can in the middle, and delaying as much as I can to the final IO stage. For IO, I marshal only the minimal required piece to a thread pool, so the loop runs pretty focused. This way, my code runs faster than anyone else’s, with decent dev efficiency, without RIIR.

So, explicitness, at start, makes your code longer, even verbose. But it is turning the symptom out, it is not the disease. When you see longer code and feel uneasy, there is nothing wrong in the thought. You should keep being slothful, just in a different direction: how can you structure the code in good sanity, when you assert explicitness? The question drives me incredibly well into a better design, always.

1 Like