This is the homepage of Peter Alexander. I am currently at Facebook working on AR/VR. Any opinions are my own.
Recent Posts
- Single-Threaded Parallelism
- unique_ptr Type Erasure
- The Condenser Part 1
- Cube Vertex Numbering
- Range-Based Graph Search in D
All Posts
Links
github.com/Poita@Poita_
Atom Feed
A Better Assert for D
Like C and C++, the D programming language provides an assert
mechanism
that allows you to check conditions at runtime, and halt execution if the
condition fails. You can optionally print out a error if you’re in a
particularly good mood.
Trying to call log(-1)
will give you an error at runtime.
In this example, I know that the argument was -1
, but what if the argument was
the result of some long calculation? It would be nice to know what was actually
passed into log
. Typically, D programmers will use std.string.format
for
this purpose:
This solves the problem, but has a couple of issues:
- You need to import
std.string.format
. - The call to
format
is a bit ugly.
Granted, these are relatively minor issues, but it would be nice if we could simply write:
C and C++ programmers typically work around this by using some clever macros, but D doesn’t have a pre-processor, so we’ll need to use standard functions.
Here’s a first attempt:
This works, giving the error:
Notice however, that the file and line number are of the assert inside
assertf
rather than log
. This is less than ideal.
Fortunately, D has a tricky little feature designed just for this. D defines
a couple of keywords, __FILE__
and __LINE__
that statically evaluate to the
file name, and line number of the locations of those keywords respectively.
Furthermore, if you use those keywords as default arguments to a function then
you get the file name and line number of the calling function. For example:
This is exactly what we need. There’s just one problem: you can’t put default arguments after variadic parameters; the compiler can’t figure out whether the last arguments refer to the variadic parameters or the optional parameters.
The way to work around this is to make the file and line template parameters, that way they can be automatically deduced, and the user won’t need to specify them manually.
Our function is working as expected now, but suffers a major, obvious flaw: it
works in -release
builds.
While D provides a standard way to detect -debug
builds (using the debug
keyword), there is currently no standard way to detect builds where an assert
should fire.
I say “currently” because I have requested the feature, and as of DMD 2.061, you’ll be able to write:
This is conditionally compile the test only in builds where asserts are meant
to fire, similar to how we can currently use version(unittest)
to detect
unit testing builds.
The final remaining difference between our function and the built-in assert is the evaluation strategy. When asserts are compiled out of your build, not only does the assert do nothing, but the arguments aren’t even evaluated. For example:
In -release
builds, this will print out nothing, however the analgous call
to our formatting assert will still print out.
The way to work around this is to use lazy evaluation.
Note the addition of the lazy
storage class in the parameter list. This tells
the compiler to not evaluate the arguments until they are used, and since the
arguments will not be used in -release
builds, they will not be evluated,
solving the final issue with our formatted assert.
That’s it! Remember, version(assert)
won’t work until DMD 2.061 (or unless
you get HEAD from git). Until then, you could either just leave it in all builds,
or use debug
to enable it only in -debug
builds.