against enforce

Jonathan M Davis jmdavisProg at gmx.com
Fri Mar 25 15:20:08 PDT 2011


> On 03/25/2011 08:21 PM, Jonathan M Davis wrote:
> >>> So, there really is no good answer.
> >>> - Jonathan M Davis
> >> 
> >> So why do you need to differentiate between assert and enforce if you
> >> can't choose, which of them should be used?
> >> 
> >> We can't really turn off both of them, and if we really want performance
> >> and no checks, we would want to turn off both of them, so they should
> >> work in the same way, shouldn't they?
> > 
> > assert and enforce serve _very_ different purposes. assert is for
> > checking the logic of your code. It can go away, so you can't rely on
> > it. It's simply for additional checks in your code to ensure that it's
> > of high quality. You can't rely on it. It's also _not_ for error
> > handling. When an assertion fails, there is a bug in your program.
> > 
> > Exceptions - and therefore enforce - are for error handling. They are
> > supposed to _always_ be there. Performance has nothing to do with them
> > (other than the fact that they can obviously harm performance, which may
> > cause you to refactor your code so that they're not necessary).
> > Typically, exceptions - and therefore enforce - are used when validating
> > input. That input is often completely dependent on the particular run of
> > the program, and bad input isn't necessarily a bug in the program at
> > all. When enforce fails, that does _not_ necessarily indicate a bug in
> > your program, and it should _not_ be used for finding bugs.
> > 
> > Input from the user is obviously always input, and you're going to have
> > to check that and throw an exception on failure rather than use
> > assertions. Input to a function which is completely local to your code
> > is not in any API anywhere and whose input is completely controlled by
> > your code should use assertions. At that point, if the function gets bad
> > input, it's a bug in your code. Also, out blocks, invariants, and checks
> > in the middle of functions typically have _nothing_ to do with input and
> > should be assertions. If they fail it's a logic bug.
> > 
> > The problem is when a function could be both used internally and used on
> > user input. For instance, iota is typically given hard-coded values -
> > iota(5, 100, 2) - but you could pass it value which was given to main -
> > iota(5, 100, to!int(args[1]). With hard-coded values, assert is the
> > correct solution. But with user input, enforce would be. So, which do
> > you do? assert or enforce?
> > 
> > In the case of iota, since it is almost always used with hard-coded
> > values and even when it isn't, it's likely used with computed values
> > rather than user input, so if it's wrong, it's a bug in the code rather
> > than bad user input. The application can check (with enforce or with an
> > if and throwing an exception or whatever) that the input is good before
> > passing it to iota if that's what it's doing.
> > 
> > With other functions though, it's less clear. And with every such
> > function, a choice must be made. Should it treat its input as user input
> > or as values local to the program? If it's user input, then it needs to
> > use exceptions. If it's local to the program (at which point a bad value
> > would be a bug in the program), then assert should be used. And when
> > you're dealing with a library, it's not as clear what the best solution
> > should be in. The fact that assertions are almost certainly going to be
> > compiled out might make it so that you want to treat input to the
> > library's API as user input rather than local to the program when you
> > would choose to have those same functions be treated as local to the
> > program if they weren't in another library (though of course, there are
> > plenty of cases where API functions should just plain be treating input
> > as user input regardless).
> > 
> > So, there is a clear and distinct difference between the intended uses of
> > assert and exceptions (and therefore enforce). They have very different
> > roles. The question then is not what their roles are but what you need a
> > particular function to do - e.g. treat it's input as user input or treat
> > it as local to the program (and therefore a bug if it's wrong).
> 
> This logic certainly looks sensible, but I cannot understand how it should
> work in practice. Say I'm implementing a little set of operations on
> decimals. Among those, some (division, square root...) will necessarily
> have to check their input. According to the rationale you expose, I should
> use assertions, since operand will nearly never be (direct) user input,
> instead be products of the app's logic. Then, what happens when div gets a
> null denominator on a release build? In practice, the issue is not that
> serious since I will certainly delegate to a lower-level func which itself
> throws. But I could also (in theory) implement it in assembly or whatnot.
> My point of view is if a func has preconditions on its args, then checkings
> simply cannot go away.
> 
> Such considerations lead me to wonder whether we should not instead use
> exceptions/enforce everywhere for actual func arg checking, and use asserts
> in unittests only. Or use them also for /temporary/ additional checkings
> during development (similar to unittests in fact).
> 
> A special case may be about checkings that control logical values or ranges
> which do not prevent the func to run. Say, a.length should logically be in
> 1..9 -- but the func can run fine anyway.

Steve has a great reply, but I'll add a bit. You use assertions where you can 
afford to not have the check take place (since, if it's compiled in release 
mode it won't). If an assertion fails, it's a bug in your code, but you can't 
rely on the assertion being there. If it's not a bug in your code if it fails, 
then you'd use an exception. Generally, you use unit testing and running the 
program in non-release mode to make sure that the assertions never fail. There 
has been some talk of adding an assertAlways which is an assertion which 
sticks around in release (presumably, it would be the same as enforce but 
throw an AssertError). But you can do that already by using enforce with an 
AssertError rather than Exception.

So, assertions are used to ensure that your code is correct, not to report 
errors.

In the case of something like dividing by 0 or other math functions that could 
be given bad values, the typical solution is to either use an assertion (or 
check nothing) and then let the caller worry about it. It would be extremely 
wasteful to have to constantly check whether the arguments to typical math 
functions are valid. They almost always are, and those types of functions 
needto be really efficient.

As Steve points out, what we really need is a way to have the caller indicate 
whether they want an in contract to be run or not (or possibly whether it 
should throw or be an assertion), but no one has ever come up with a good way 
of doing that AFAIK. Stuff like in contracts are an implementation detail of a 
function and are frequently compiled out. You'd have to no longer quite treat 
a function as something that's simply called and returns a value. You'd need 
it to have the concept of validation code which went along with the function 
and which the caller could choose to call or not. And I'm not sure that you 
can really do that with the C linking model.

- Jonathan M Davis


More information about the Digitalmars-d mailing list