From what I read I seem to understand std.debug.assert
can help the compiler optimizing.
is not at all like C asserts, but always called.
but can be optimized away often.
In an earlier discussion here on the forum there were some thoughts about it and Andrew even mentioned a Post Traumatic Stress Syndrome regarding asserts.
In my code I would like to give the compiler a chance to optimize using asserts, but as a simple programmer I have no clue if the assert is actually called, if it is optimized away and if it is used to optimize code!
So I often prevent explicitely an assert call by:
if (comptime lib.is_paranoid) {
assert(something);
assert(something_else);
}
because some of these checks are expensive debug-only checks.
In my feeling a built-in @assert would be a very nice addition to the language if it:
is never executed in fast releasemode.
can be used to optimize.
For example I have
if (comptime lib.is_paranoid) {
assert(input_depth >= 0);
}
where I probably deprive the compiler of a chance to optimize things that are done with input_depth (and other vars or code depening on this) in the function.
I already tried to spark a conversation about it, and I also do believe a builtin assert, would be both more convenient, and more powerful, I also think itās not fair that we can encode some of the invariants with the @branchHint() but not more invariants with an @assert() or @excpect() builtin
This unreachable is exactly what causes the optimization in release-fast because it promises the optimizer that this code will in fact not be reached which in turn allows further optimizations (and if it actually is reached than youāll get undefined behaviour.
In some (many? most?) cases the compiler might already be able to remove the condition because the only result of the condition is the unreachable anyway, but only when the condition has no āside effectsā.
PS: I donāt really have an opinion about an @assert() builtin, but I feel like Zig already has way too many builtins. I would not change the semantics of assert though because that would cause even more confusion. Ideally stdlib.debug.assert would be called something entirely else IMHO.
Maybe but as stated: I donāt know what the compiler can do.
Indeed there are quite an insane amount of builtins already, certainly regarding unreadable math
It would be nice to have one @assert I believe, which tries to optimize.
Another example from my code is:
if (comptime lib.is_paranoid) {
assert(us.e == self.stm.e); // very cheap, probably some magic optimization possible.
assert(self.pos_ok()); // very expensive, optimization completely impossible and irrelevant.
}
Wouldnāt it be nice if the compiler could figure it out?
@assert(us.e == self.stm.e); // Hey compiler, try something.
@assert(self.pos_ok()); // Compiler sees there is nothing here to try.
I think it might be more useful to have a @hasRuntimeSafety() built-in for this. You could then easily use that in conjunction with the current std.debug.assert:
std.debug.assert(!@hasRuntimeSafety() or expensiveLogic());
or even just write a section of code that only ever runs in safe modes:
if (@hasRuntimeSafety()) {
for (blah) |b| {
if (b.isExpensivelyWrong())
unreachable;
}
}
Iām not sure thereās many cases where that would be useful. Iām not how sure of my reasoning is but hereās what I think:
If it canāt be optimized out, evaluating its argument must have side effects,
If the assert weāre giving the compiler depends on side-effects, it canāt properly use it for optimization
There is certainly some niche cases out there (and maybe Iām wrong and there are many non-niche cases), but either the assert is useless or optimized out.
Personally, Iāve never used much assertions outside of Zig, and itās really natural to me to not think of it as a āalways-zero cost callā, because itās just a function.
Iād be more interested in proper documentation and some idiomatic way to ensure the assertion doesnāt depend on side effects.
Deprecated because it returns the optimization mode of the standard library, when the caller probably wants to use the optimization mode of their own module.
EDIT addendum: I would also like @hasRuntimeSafety() to be based not only on @import("builtin").mode, but on @setRuntimeSafety(bool) as well.
Hate it or love it, but itās not going to change as long as @andrewrk is in charge.
The assert statement in other languages is either macro based or breaking the ānormalā rules of those languages: you canāt pass a boolean into a function and expect the compiler to magically pretend you didnāt. Since zig doesnāt have macros and insists on having no magic regardless of how convenient it is you will find no argument that will sway him.
His logic is undeniable correct, but it fundamentally changes the functionality of an extremely established programming trope. Honestly Iād almost prefer to not have assert in zig at all. Hopefully one day I will admit he was right on this call.
Is about perfect four our use-case, and a big improvement over your typical assert macro. Itās just a function, canāt get confused about that! Having to write if (constants.verify) assert(costly()) is a positive feature in our context.
Though, Iād probably move it to std.assert rather than std.debug.assert. Having debug in there feels weird (the same goes for std.debug.panic).
Yes, this is the key point to understand. Because of this, assert calls should not be removed in release-fast.
Note that assert! calls are never removed in Rust (in spite of being a macro) and that has the same benefits in Rust. There are other differences with Rust assert!, but this aspect is the same.
I would think of @assert as a sort of @TypeOf, analyzing during compilation but not evaluating during execution the expression itās been given. Wouldnāt that fit?