I am testing some routines which I need to assert, but I only want this if the program (debug compile) is in ‘paranoid’ mode.
These paranoid assertions should only be executed when my global const paranoid is true.
How can I best write a routine paranoid_assert() of which I am sure it is totally ignored in release mode?
Normal asserts are ignored in releasemode.
(this is kinda needed because the paranoid checks in some cases are slowing down very much).
the simplest way is to import builtin and to check if your program is being built in debug mode, and only perform the check in that case. for more granular control you can add a build-time setting (see Zig Build System ⚡ Zig Programming Language) but the gist is the same.
const builtin = @import("builtin");
fn debugAssert(ok: bool) void {
if (builtin.mode != .Debug) return;
if (!ok) unreachable;
}
const paranoid = @import("paranoid").paranoid;
// Whenever you need to assert something in paranoid mode:
if(comptime paranoid) std.debug.assert(assertion);
You can’t simple pass the assertion into the function, because the work to compute the parameter would have already been done, so you need to wrap it in a conditional at the call site.
You can’t simple pass the assertion into the function, because the work to compute the parameter would have already been done, so you need to wrap it in a conditional at the call site.
Hmm that’s a good point i didn’t think about. I was kind thinking that if the condition was dependent on a comptime known value it would be removed.
Yes, that would work as well, but only for expensiveCheck. Regardless of whichever method you choose, for every check that you want to make conditional, you need a way to divert control flow before reaching the computation. You can put it in a function or do an inline block of code. What you can’t do is have a function that takes an arbitrary statement as a parameter and conditionally executes it. This goes agaisn’t the principal of “no hidden control flow”. Consider what would happen if such a function existed:
paranoidAssert(functionWithSideEffects());
Without knowing what paranoidAssert is doing internally, one would expect the side effects to happen. It would be very surprising and hidden if it could make the entire call to functionWithSideEffects disappear.
Taking a page out of asynchronous programming, what you could do is pass a function object:
/// `assertion` must have a method `call`
/// that returns a bool, and will be called
/// if paranoid assertions are enabled.
fn paranoidAssert(assertion: anytype) void{
if(comptime paranoid)
std.debug.assert(assertion.call());
}
// Call site
paranoidAssertion(struct{
// arguments
arg1: u8,
pub fn call(self: @This()) bool{
//Do the checks
}
}{ .arg1 = 1});
But it’s a bit clumsy. Better to just do if(comptime paranoid) thing.
This fine and probably the most concise way of eliding the expensive checks when assertions are disabled. However, you could also consider something like the following, where the assertion function is an optional that you have to unwrap:
This is slightly more verbose, but leaves less room for programmer mistakes since the captured assert is guaranteed to only be available in that scope.