[ANN] SmartAsserts: A drop-in replacement for `@assert` that prints out additional information upon failure

Hi all, I just published SmartAsserts to the general registry. It provides a convenient macro @smart_assert that automatically prints out argument values upon assertion failure (without re-evaluating the original expression) and can be used as a drop-in replacement for @assert. Unlike @assert , these smart assertions can also be easily turned off at compile-time. Hope you will find it useful!

An assertion failure example using @smart_assert:

julia> let a = 1.0, rtol=0.1
           @smart_assert isapprox(a, sin(a), atol=0.05; rtol)
       end
ERROR: AssertionError: Condition `isapprox(a, sin(a), atol = 0.05; rtol)` failed due to:
        `a` evaluates to 1.0
        `sin(a)` evaluates to 0.8414709848078965
        `0.05` evaluates to 0.05
        `rtol` evaluates to 0.1
Stacktrace:
 [1] top-level scope
   @ REPL[177]:2
14 Likes

One feature I’d like to have is to disable assertions in release mode, like in C. I know there are tricks to enable this, but it would be a nice built-in feature.

One easy way to implement this would be via an enabled::Ref{Bool} that is checked at run-time and skips the tests if set to false. However, there would still be a small amount of overhead due to this run-time check even if it’s set to false. Would this be sufficient for your common use cases?

Alternatively, to truly get rid of any run-time cost, I can add an enabled keyword argument to the macro, but the user will need to make sure it’s set to a compile-time constant.

Another solution that’s more similar to what you described would be using a constant ENABLE_ASSERTS=false defined inside module M to control whether to disenable all smart_asserts inside M. This way, the package author can easily control whether to turn off all their smart_asserts by setting the value of this constant inside their module (e.g., via environment variables using const ENABLE_ASSERTS = ENV["ENABLE_ASSERTS"]).

@static if is evaluated at compile time, but I’m not sure how it can help in this case.

@debug @assert
1 Like

I think under the hood this one still relies on run-time level checks. The code generated by @debug contains the line if std_level >= _min_enabled_level[] ....

Ok, I just released a patch to support turning off the assertions at compile time.

1 Like

There is a PR to Base to do that for @assert, by the way: RFC: implement proper debug mode for packages (with support for re-precompilation) by KristofferC · Pull Request #37874 · JuliaLang/julia · GitHub

2 Likes

There is also ArgCheck.jl:

julia> using ArgCheck

julia> a = 1.0; rtol = 0.1
0.1

julia> @check isapprox(a, sin(a), atol=0.05; rtol)
ERROR: CheckError: isapprox(a, sin(a), atol = 0.05; rtol) must hold. Got
rtol => 0.1
a => 1.0
sin(a) => 0.8414709848078965
Stacktrace:
 [1] top-level scope
   @ ~/.julia/packages/ArgCheck/5xEDR/src/checks.jl:243
2 Likes

Wow, I did not know it exists! Thanks for the info! Looks like ArgCheck’s @check does something very similar to my @smart_assert! Maybe I should merge my package into that one instead? Most of my code is very similar to theirs anyway…

2 Likes

I noticed that in the result produced by @check, the stack trace seems to point to ArgCheck’s source location instead of the assertion’s. Also, the values of keyword arguments are printed out before the positional args. Would you consider these as issues that are worthwhile to fix? If so, I’d be happy to make a pull request (I have addressed these in @smart_assert and I think they should be easy to fix in ArgCheck as well).

3 Likes

Sure please go ahead and fix these!

2 Likes