Proper Use of Assert and Enforce
Jonathan M Davis
jmdavisProg at gmx.com
Wed Mar 14 01:02:53 PDT 2012
On Wednesday, March 14, 2012 06:44:19 Chris Pons wrote:
> I'm new, and trying to incorporate assert and enforce into my
> program properly.
>
> My question revolves around, the fact that assert is only
> evaluated when using the debug switch.
assert has nothing to do with the debug switch. All the debug switch does is
enable debug blocks. e.g.
debug
{
writeln("A debug message.");
}
If you put an assertion in the debug block, then it'll only be there when
you compile with -debug like any other code, but if you put it outside of
the debug block, it won't be affected by -debug at all. Rather, assertions
are compiled in unless you compile with the -release flag.
> I read that assert throws
> a more serious exception than enforce does, is this correct?
Not exactly. Have you used assertions in other languages? In most languages,
an assertion just outright kills your program if it fails. An assertion
asserts that a particular condition is true with the idea that if that
condition is _not_ true, then there is a bug in your program. As such, you
don't want your program to continue if it fails. It's used to verify the
integrity of your program. in and out contracts use them as do invariants
and unittest blocks.
When an assertion fails, an AssertError is thrown, _not_ an Exception.
Throwable is the base class of all exception types, and Error and Exception
are derived from it. Errors are for conditions which are normally considered
fatal and should almost never be caught (for instance, trying to allocate
memory when you're out of memory results in an OutOfMemoryError being
thrown). Unlike Exceptions, Errors can be thrown from nothrow functions, and
the language does not actually guarantee that scope statements, destructors,
and finally blocks will be run when an Error is thrown (though the current
implementation currently does run them for Errors and there is some
discussion of actually guaranteeing that it always will; regardless,
catching Errors is almost always a bad idea).
Exception and any types derived from Exception cannot be thrown from nothrow
functions and _are_ guaranteed to trigger scope statements (aside from
scope(success)), destructors, and finally blocks. They are generally meant to
be recoverable and catching them is fine. They are generally used to indicate
that a function is unable to properly perform its function given the current
state of the program but _not_ because of a logic error. Rather, it's
because of stuff like bad user input or because a file doesn't exist when it's
supposed to, so opening it fails. Exceptions are used for reporting error
conditions to your program. It can then either choose to catch them and
handle them or let it percolate to the top of the program and kill it. But
unlike Errors, it _is_ okay to catch them, and it's intended that they not
indicate an unrecoverable error.
So, you use an assertion when you want to guarantee something about your
program and kill your program when that condition fails (indicating a bug in
your program). The checks are then normally compiled out when you compile
with -release. So, the idea is to use them to catch bugs during development
but to not have them impede the performance of the program when you release
it. The only assertions that are always left in are the ones that can be
determined to be false at compile time (generally assert(0) and
assert(false), though stuff like assert(1 == 0) also qualify, since their
result is known at compile time). They're used when you want to guarantee
that a particular line is never hit (e.g. when a switch statement is never
supposed to hit its default case statement). The compiler actually inserts
them at the end of non-void functions so that if the program somehow reaches
the end without returning, the program will not try and continue.
Exceptions, on the other hand, are _not_ used for bugs in your code, but
rather error conditions which occur do to circumstances at runtime which are
not controlled by your program (like bad user input). enforce is simply a
way to make throwing exceptions look like assertions and save a line of
code. So, instead of
if(!condition)
throw new Exception(msg);
you do
enforce(condition, msg);
And if you want to throw a specific exception type, you do either
enforceEx!SpecificException(condition, msg);
or
enforce(condition, new SpecificException(msg));
> I'm trying to use enforce in conjunction with several functions
> that initialize major components of the framework i'm using.
>
> However, i'm concerned with the fact that my program might
> continue running, while I personally would like for it to crash,
> if the expressions i'm trying to check fail.
>
> Here is what i'm working on:
>
> void InitSDL()
> {
> enforce( SDL_Init( SDL_Init_Everything ) > 0, "SDL_Init
> Failed!");
>
> SDL_WN_SetCaption("Test", null);
>
> backGround = SDL_SetVideoMode( xResolution, yResolution,
> bitsPerPixel, SDL_HWSURFACE | SDL_DOUBLEBUF);
>
> enforce( backGround != null, "backGround is null!");
>
> enforce( TTF_Init() != -1, "TTF_Init failed!" );
> }
>
> Is it proper to use in this manner? I understand that I shouldn't
> put anything important in an assert statement, but is this ok?
Shouldn't put anything important in assert statements? I'm afraid that I
don't follow. Assertions are compiled out when you compile with -release, so
they cannot be used when you want to guarantee that they fail even with -
release or if you have code in them that must always run. So, for instance,
if the foo function _had_ to be run, doing
assert(foo() == expected);
would be bad. You'd do
immutable result = foo();
assert(result == expected);
instead. The same concern does not exist with enforce, however, since it's
never compiled out.
As for whether assert or enforce should be used, does failure mean a bug in
your program or simply that you've hit an error condition. From the looks of
your code, it would probably simply be an error condition in this case,
since your verifying that some initialization functions succeed. If they
fail, it's not a bug in your program but rather that something else
(probably outside of your control) went wrong. So, enforce is the correct
choice. And if nothing calling initSDL (directly or indirectly) catches the
exception, then the exception will kill your program, but it can be caught
with a catch block if you choose to. If you want to _guarantee_ that the
program dies if any of them fail, then you should probably create a subclass
of Error (e.g. SDLInitError) and throw that, in which case, your first
enforce would become
enforceEx!SDLInitError(SDL_Init(SDL_Init_Everything) > 0, "SDL_Init
Failed!");
Hopefully I didn't overload you too thoroughly and this at least gives you a
better idea of the difference between assertions and exceptions (and
therefore between assert and enforce). Assertions are for verifying the
integerity of your program and failure means that your program has a bug,
whereas exceptions are for reporting error conditions which do _not_
indicate a bug in your program.
- Jonathan M Davis
More information about the Digitalmars-d-learn
mailing list