From my understanding, assume and assert are two pretty important words for documenting functions, assert referring to assumptions about the inputs the function checks, and assume referring to those it does not.
Generally, one would expect them to be pretty easy to differentiate, but the following code is giving me trouble:
On the assume side, I certainly doesn’t look like an assert, does it? No assert function or macro or whatever else is being invoked, and the unreachable really does make it look like an unchecked assumption.
On the assert side however, zig’s assert function is just an if (!ok) unreachable;, which means this code should be equivalent to doing an explicit assert call before the switch.
Personally, I feel I fall more on the calling it assert side, but the logic behind assume also makes too much sense for me to feel confident just going with it.
Ultimately it’s just semantics, but I am curious what others think!
In safe modes (Debug, ReleaseSafe) unreachable will trigger a panic if it is reached
in unsafe modes (ReleaseFast/Small) it will be assumed to be true and used as an optimisation hint.
That is the typical behaviour you want for an assert, with the caveat that it is a normal function in zig, not a macro, so you have to be aware if your input has side effects!
As well as the caveat of @setRuntimeSafety.
I am not sure why “assume” is even a thing? Why would you want an optimisation hint that isn’t safety checked in safe modes
How I see it, it is not the compiler that does the assertion, but you assert to the compiler that the condition is true. Depending on the release mode it will check your assertion or use it for optimization.
I’ve been staring at the maybe() function for a good minute or so and still don’t know if it’s a joke or not tbh …
(as for the general discussion: I think the important thing to know is that a Zig “assert” turns into an “assume” in ‘unsafe-release’ mode (in sense of Clang’s __builtin_assume builtin), which can be a footgun when not aware of that little detail).
My understanding of it is that assertion violation will be caught in safe modes, but assumption violation might not. It doesn’t matter in unsafe modes.
Under this definition, unreachable never being reached is always an assertion.
If you don’t see an assert(condition) in the code, does it mean that the assert would be invalid, or that that the assert would be valid, but is missing because the original author forgot to add it? maybe disambiguates intentional vs unintentional omission.
Here’s a good example from our protocol aware recovery implementation:
For every log record, a replica must communicate whether it has that record, and whether it might have acknowledged the fact that it has the recored.
Counter-intuitively, it is a possibility that replica has a particular prepare, but, simultaneously, is sure to have never ack’ed it before. With that maybe, the reader immediately sees that this is a possibility, and then can check the context for // Nack bit case 2 comment that gives rise to this situation.
Without that maybe, the reader would totally assume that present implies !nack (don’t ask me how I know that).
I’m getting a lot of answers regarding how unreachable works across different optimization modes and similar, so I’ll restate: this is a question about documentation.
That is, when documenting the function that contains the code block from the initial post, would you say it asserts the string it’s iterating over contains only decimal digits and a point, or that is assumes that?
Sorry for derailing the thread with my seriously silly jokes!
I would say that “assert” is a subset of “assume”, which additionally implies presence of safety check (that is, a guarantee that, if you misuse the function, and you are compiling in ReleaseSafe, you’ll crash, rather than do arbitrary bad behavior).
So, for the above function, both “assert” and “assume” are valid, “assert” is more precise, but if, at some point, you’ll want to do crazy simd optimizations such that the function will just run out of the end of the array without any safety checks, if the assumption doesn’t hold, then “assume” will be more forward compatible (you can still assert if you “assume”, you just don’t promise that)
To rephrase my point a bit: in my opinion you are not documenting that the function asserts or assumes anything. You are documenting your assertion to the compiler (and the reader). So I understand it as an assertion always. What the compiler does with that assertion depends on the release mode (but you basically should not care).
Use the word assume to indicate invariants that cause unchecked Illegal Behavior when violated.
Use the word assert to indicate invariants that cause safety-checked Illegal Behavior when violated.
Reaching unreachable invokes safety-checked Illegal Behavior, i.e. it panics in Debug/ReleaseSafe, so the example in the OP would fall under the “assert” classification.
Don’t worry, it seems everyone misunderstood what I was going for :p
Also interesting thought about forward compatibility of the terms, I wouldn’t have thought of that. Luckily I don’t think it would make much sense to go for anything other than a plain loop for my usecase.