I roughly understand the OP’s question. It stems from a misunderstanding of operator precedence. The OP mistakenly assumes that += has higher precedence than + and -. However, + and - actually have higher precedence than += and -=.
This leads the OP, assuming the misunderstanding of += operator precedence, to misattribute the difference in zig’s behavior from C, assuming that the difference lies in operator precedence. However, the real difference is that zig doesn’t use wraparound.
Edit: nope, forgot that Zig made bitwise operators not do the crazy thing which messes up your code when combined with comparators. But not relevant to the thread.
To fix 1. you should keep this in mind and decide what operations you actually want and if you really want wrap around you have to opt-in.
About 2. I suggest to consider this a strict improvement that the compiler tries very hard to optimize away constant values. And that it told you with a compile error that subtracting -1 from 0 is not gone work without wrapping around.
For instance, wraparound arithmetic is essential for hash functions, and those can be translated directly from another language simply by using the % variants of the arithmetic operators.
Saturation is frustratingly difficult to express without saturating operators, and when you need it, you really need it.
Ultimately, the compiler can’t tell if an overflow is a mistake, or intended behavior. They mean different things, so Zig forces code to be explicit about it, and checks that the contract is observed in safe modes.
There’s a similar thing, where languages disagree on the correct value for signed integer division, when the divisor is negative. So in Zig you have to use one of several builtins which make it clear which one is meant.
I do wish we had /% and /- instead of having to call them @divTrunc and @divFloor though. The modulus equivalents aren’t as bad, but it’s awkward-looking doing division with a function call, and it messes up precedence, end up putting things ‘inside’ which seem like they should be ‘outside’.
Still better than assuming it’s going to do one thing, then having it do something subtly different.
Yes, I agree. It feels a bit strange at first, but once you get used to it and realize how often these nuances are actually important in your own code, it becomes something that is much appreciated. It just takes “unlearning” some of C’s quirks that we have all become accustomed to and deem as “normal”, when they are actually quite serious footguns that have been the source of countless (often difficult to find) bugs.
Here, max_u32 + some should also be saturating. Also, you tried to use +|= on initial value, which is compiler error. The line you meant is const saturated = max_u32 +| some;
That is incorrect. In standard C, a += expr; is exactly equivalent to a = a + (expr);
So in this case it becomes a = a + (b - 1); not (a + b) - 1. This equivalence holds in all C standards (C90–C23).
Edit: Your example only works because in C, arithmetic with unsigned ints always runs modulo 2^n. Negative values are neatly folded back into the numeric range.