Zig Assertion Semantics

Consider the following two snippets of code in Zig.

// version 1
foo(bar());

// version 2
const temp = bar();
foo(temp);

It's pretty clear that those snippets do the same thing, even if foo() and bar() have side effects.

But now if I rename one of these functions, it seems to cause confusion:

// version 1
std.debug.assert(bar());

// version 2
const temp = bar();
std.debug.assert(temp);

If you're used to C or some other languages, you may think that assert can break the rules. You may think that version 1 is at risk of losing bar()'s side effects when asserts are disabled, and version 2 is safe because it moves the call to bar() outside of the assertion to preserve it.

Zig is consistent, so both versions still obviously behave the same, even when I've swapped out the name foo to std.debug.assert. You can trust that there's no random compiler magic that eliminates bar()'s side effects. Even if you were to replace assert with an empty function, bar()'s side effects are still guaranted to happen.

Actually, in ReleaseUnsafe, the assertion won't be replaced with an empty function. The implementation will still be if (!ok) unreachable;, except in ReleaseUnsafe, that becomes an unchecked unreachable. Because it's unchecked, the compiler can assume the condition is true, so condition doesn't need to be computed, and the compiler can work backward from there, eliminating everything involved in computing bar() that doesn't have side effects, and eliminating anything that only happens when bar() is false. However, bar()'s side effects, if any, will still happen.

Also see: