Helping the compiler with assumptions while using exceptions

Jonathan M Davis jmdavisProg at gmx.com
Mon May 30 13:49:04 PDT 2011


On 2011-05-30 12:49, simendsjo wrote:
> I'm having some problems trying to get the best of both worlds here.
> 
> void f(Class c) {
>    assert(c != null);
>    // use c
> }
> 
> In this example, we tell the compiler that c is never able to be null.
> The compiler can use assertions like this for optimizations (not sure if
> dmd does this though).
> 
> But assert is only a debugging tool.
> Say we wanted to have this check at runtime too - just in case - so we
> can fail where the problem is.
> 
> So we do this:
> 
> void f(Class c) {
>    enforce(c != null);
>    // use c
> }
> 
> But now the compiler has no idea c will never be null later on (or does
> it...?).
> 
> We could always do this:
> 
> void f(Class c) {
>    assert(c != null);
>    enforce(c != null);
>    // use c
> }
> 
> But this is overly verbose.
> 
> Or is this not a problem at all? E.g. Use enforce for runtime checks -
> the compiler understands them/won't use asserts for optimizations anyway?

assert and enforce serve two very different purposes. assert is used for code 
verification. You can't assume that it's always be there, because it might be 
compiled out. As such, the compiler is not going to use it for any 
optimizations. If an assertion fails, there's a bug in your program. It's used 
to detect bugs.

enforce and exceptions, on the other hand, are _not_ intended for bug 
detection. They are for error handling. They're generally used in cases where 
you want to ensure that something is true but where you can't guarantee it 
(such as when dealing with user input or the file system). You throw an 
exception if the condition isn't true, and then you deal with it however is 
appropriate. Generally-speaking, an exception does _not_ indicate a bug. It 
indicates an error, but that doesn't mean that there's anything wrong with 
your program. Rather, there was a problem with its input.

As such, if you were to use assert in the above code, then you're saying that 
it's a bug in your program if c is null and having that check go away in 
release mode is fine, because you're done looking for bugs. It's assumed that 
the code is correct enough that c will never be null. If, you use enforce, on 
the other hand, you're saying that it could be perfectly valid for c to be 
null, but you don't want your f function to ever execute with a null c, so you 
throw if c is null. Then whatever calls f can handle it in whatever way is 
appropriate, possibly resulting in an error being printed to the user or 
another attempt to call f with another value for c which isn't null or 
whatever is appropriate for your program. By using enforce, you're saying that 
it's not a bug for c to be null but that it _is_ an error (likely caused by 
input in some manner) if it occurs.

What assert and enforce are doing here are completely different. You shouldn't 
be choosing between assert and enforce based on whether they'll be around in 
release mode or not. They're two completely different types of error handling.

Now, if it's a bug in your code if c is null, and you want to be absolutely 
certain that c is never null - even in release mode - then you can use 
assert(0). e.g.

if(c is null)
	assert(0, "c cannot be null!");

assert(0) is translated to a HLT instruction in release mode. So, it'll kill 
your program if it's hit. It's intended for cases which should _never_ happen 
and you want to be absolutely sure that they never do (such as a default case 
statement or else clause which should never be hit as long as your logic is 
correct). It's still for bug detection like normal assert, but it's going the 
extra step of saying that if this line of code is ever reached in release 
mode, kill the program. It's still completely different from enforce and 
exceptions which are runtime errors which are potentially handleable.

- Jonathan M Davis


More information about the Digitalmars-d-learn mailing list