Let’s do another take on what @dimdin has already presented here and look at another common overload: +
const z = x + y;
First, in an overloaded language, +
can do anything. It can make syscalls, allocate memory, you name it. It’s worse though - all of those overloads can fail and throw. This is part of the reason that many large software companies outright prohibit writing exceptions in their code bases. In Zig, +
means addition in the typical sense.
Now, I think it’s very important to be honest about what things mean, even though I genuinely prefer this language. You’re asking a very pertinent question and I think it deserves careful consideration.
You can absolutely write software that hides control flow in any systems programming language.
In a current version of a project I’m working on, if you run out of device memory, the program closes. In this sense, functions do have “side-effects” - it could return something or shut down the program. I think it’s fair to say that’s a form of hidden control flow - it’s not obvious from the call site that could happen.
Now… the question is, what does that say about Zig?
Zig gave me the tools to make my own decisions about that and they are very apparent. I’ve had to make that decision consistently knowing full well that I’ll either continue that way or come up with an auxiliary solution in the future. That said, Zig makes it extremely apparent where I am making this decision.
Let’s go back to that plus example from above:
auto z = x + y;
Can this throw? Maybe, I have no clue what plus means in this context because it could be overloaded. It could throw the kitchen sink for all I know. There’s nothing here that says “I can be a problem”. I like to contrast things to C++ in cases because I regularly come across this issue at work. You’ll see this happen alot:
auto p = new int(42);
assert(p != nullptr);
They’re trying to be a good citizen and put some debugging around nullptrs. The problem is that new
throws. If it fails, we’ll never even hit that assert. This goes really deep though. For example, std::variant
throws if you try to access the wrong member. Just like in the last example, you’d never know that from the call site.
Again, it’s possible to write software that hides control flow - if we couldn’t, I don’t see how libraries would even be possible considering that they expose an interface and do things on the backend for you. The question though is does the language itself (and I’ll add standard utilities) present you with this information forthrightly?