[phobos] enforce() vs. assert() for range primitives

Andrei Alexandrescu andrei at erdani.com
Sat Aug 21 20:18:35 PDT 2010


On 08/18/2010 05:49 PM, David Simcha wrote:
> While I was debugging std.range and std.algorithm I noticed that there's
> tons of inconsistency about when enforce() vs. assert() is used for
> checking input to range primitives. I think this needs to be made
> consistent, and there are good arguments for both.

As mentioned, I've studied this exchange, thought a lot about it, and 
also discussed it with Walter.

Clearly right now Phobos is inconsistent. Walter and other used asserts 
and contracts and other means etc. My approach since I started has been 
to sprinkle enforce() everywhere and worry about it later, and that 
later has come.

One problem with that approach is that, as Lars mentioned, there are 3-4 
checks on a simple operation due to the use of higher-order ranges that 
wrap other ranges, like Stride!Chain!Whatever. This issue is not 
difficult to tackle: we can introduce in Phobos the notion that a range 
wrapping another range defers the checking to the wrapped range. That 
way the wrapper only uses assert() and essentially trusts that the other 
range protects its own integrity. Consider as an example Take's 
implementation of popFront:

     void popFront()
     {
         enforce(_maxAvailable > 0,
             "Attempting to popFront() past the end of a "
             ~ Take.stringof);
         original.popFront();
         --_maxAvailable;
     }

The enforce is redundant - we could decide that Take defers checking to 
original. So this code should be allowed:

     void popFront()
     {
         assert(_maxAvailable > 0, ...);
         original.popFront();
         --_maxAvailable;
     }

But (very careful) Take should still protect its _own_ integrity, 
assuming original also does. So the following code would be incorrect:

     void popFront()
     {
         assert(_maxAvailable > 0, ...);
         --_maxAvailable;
         original.popFront();
     }

If original.popFront throws, _maxAvailable has already been decremented 
so Take is in an inconsistent state. So rule #1 could be something like:

Rule #1: A higher-order entity in Phobos can generally assume that the 
lower-order entities it builds on protect their own integrity, and 
protect its own under that premise.

I am sure there would be fuzzy corners to this rule, but by and large it 
shold cut down on a lot of enforce calls. (We could and should replace 
them with asserts).

The second rule concerns safety. To my dismay, this program still 
compiles and runs "successfully" with -release:

import std.stdio;
void main(string[] args)
{
     writeln(args[10]);
}

It should throw, and it should only run with -noboundscheck enabled. 
That flag was defined by Walter under my pressure, and I think it's 
misnamed - it should really be -safe.

Safety is one matter I am extremely rigid about. There is a point where 
we must draw a line in the sand regarding checks, and safety is it. 
Since there is a simple and clear definition of it, we can't redefine it 
as we'd prefer. So I'd say:

Rule #2: All checks protecting memory safety must be done with enforce() 
or rely on built-in array indexing (which in turn obeys -noboundscheck).

Finally, we have several places where we call APIs such as the I/O code 
etc. For those I think it's fine to mandate use of enforce() for several 
reasons, a simple one being that such calls would not be slowed down 
considerably by the extra test. So:

Rule #3: All external API calls must be protected by enforce().

Of course there are many case-by-case judgments, but I think having such 
guidelines in mind should be helpful. Thoughts?


Andrei


More information about the phobos mailing list