Operators are a red herring here; what Zig disallows is overloading of names, period. Yes, overloading is resolved statically at compile time, but that's not the point (Zig does allow dynamic dispatch). The point is that a simple understanding of what the code does requires information available outside the unit (subroutine or even file). A subroutine can contain two references to `foo` resolving to two different targets, but you can't know that from the code in the subroutine. The meaning of a name must be defined in one place in Zig, and a name can only refer to one thing.
We could certainly argue on whether disallowing overloading is helpful, but it's first important to understand what the principle is, and it's not about the operators.
> The point is that a simple understanding of what the code does requires information available outside the unit (subroutine or even file).
No, it doesn't. Knowing what function "+" resolves to requires only the type of the left-hand side (or, I think, the RHS as well in Haskell). Absent overlapping instances (which Rust doesn't support, and Haskell only supports with an extension), there can only be one such function implementation per type. And, since top-level items are fully type-annotated in Rust (and idiomatically annotated in Haskell), this is a completely local determination. Therefore, for every occurrence of "+" in the source, it's completely unambiguous which implementation "+" calls, and this can be determined solely by looking at the calling function.
The end result: Rust, like Zig, disallows overloading of names, but Rust also effectively supports overloaded operators via traits (typeclasses).
> there can only be one such function implementation per type
Right, and that's more than one definition per name. In Zig, a name refers to only one definition. (Plus, recall that in Zig the type of a target isn't always known when reading the code, probably more frequently than in C++ or Rust, where this only happens in macros.)
> disallows overloading of names
That the definition referred to by an identifier depends on the type is what overloading means. Overloading is the very essence of traits.
> in C++ or Rust, where this only happens in macros
Sorry, this is wrong. In both of these languages it happens much more frequently. All the time, in fact, whenever you either infer return types or just chain calls on return values, or when the type is a generic/template type parameter. So when you see `foo(e)` or `e.foo()`, where `e` is some expression, you can only tell which definition of `foo` is referenced -- or even whether two instances of `foo` refer to the same definition or not -- by figuring out the type of `e`, which is often implicit in the code.
> Overloading is the very essence of traits.
Which is why in Zig, the mechanisms that perform a similar task to that of traits -- i.e. abstracting over types -- work differently, without overloading.
As a general rule, Zig does not want the semantics of a piece of code to depend on things the compiler might know but the reader of the unit might not. Whether or not such a degree of explicitness is a worthy principle for a low-level language is a matter of taste, I suppose.
We could certainly argue on whether disallowing overloading is helpful, but it's first important to understand what the principle is, and it's not about the operators.