Version 4 of Assertions

Updated 2004-11-19 15:35:59

if 0 {Richard Suchenwirth 2004-11-18 - Checking conditions is a frequent operation in coding. Absolutely intolerable conditions can just throw an error:

   if {$temperature > 100} {error "ouch... too hot!"}

Where the error occurred is evident from ::errorInfo, which will look a bit clearer (no mention of the error command) if you code

   if {$temperature > 100} {return -code error "ouch... too hot!"}   

If you don't need hand-crafted error messages, you can factor such checks out to an assert command:}

 proc assert condition {
    if {![uplevel 1 expr $condition]} {
        return -code error "assertion failed: $condition"
    }
 }

if 0 {Use cases look like this:

   assert {$temperature <= 100}

Note that the condition is reverted - as "assert" means roughly "take for granted", the positive case is specified, and the error is raised if it is not satisfied.

Tests for internal conditions (that do not depend on external data) can be used during development, and when the coder is sure they are bullet-proof to always succeed, (s)he can turn them off centrally in one place by defining

 proc assert args {}

This way, assertions are compiled to no bytecode at all, and can remain in the source code as a kind of documentation.

If assertions are tested, it only happens at the position where they stand in the code. Using a trace, it is also possible to specify a condition once, and have it tested whenever a variable's value changes:}

 proc assertt {varName condition} {
    uplevel 1 [list trace var $varName w "assert $condition ;#"]
 }

if 0 {The ";#" at the end of the trace causes the additional arguments name element op, that are appended to the command prefix when a trace fires, to be ignored as a comment.

Testing:

 % assertt list {[llength $list]<10}
 % set list {1 2 3 4 5 6 7 8}
 1 2 3 4 5 6 7 8
 % lappend list 9 10
 can't set "list": assertion failed: 10<10

The error message isn't as clear as could be, because the [llength $list] is already substituted in it. But I couldn't find an easy solution to that quirk in this breakfast fun project - backslashing the $condition in the assertt code sure didn't help. Better ideas welcome.

In any case, these few lines of code give us a kind of bounds checking - the size of Tcl's data structures is in principle only bounded by the available virtual memory, but runaway loops may be harder to debug, compared to a few assertt calls for suspicious variables:

 assertt aString {[string length $aString]<1024}

or

 assertt anArray {[array size anArray] < 1024*1024}

FPX notes that assert(), in the C and C++ programming languages, is a debugging aid during development. Assertions are usually disabled in production code. When the NDEBUG preprocessor symbol is defined, assertions are not evaluated.

Tcllib's control::assert is similar, in that assertions can be enabled and disabled.

In that light, assertions should only be used for "sanity checks," to validate the developer's logic and algorithms under development. Error conditions that might reasonably occur at runtime, such as the failure to open a file, or the aforementioned example of excessive ambient temperature, should be handled by a regular error.


Tcllib has a control::assert with more bells and whistles. See also Type checking - Constraining variables


Category Concept | Arts and crafts of Tcl-Tk programming }