Against enforce()
Steven Schveighoffer
schveiguy at yahoo.com
Fri Mar 18 10:25:23 PDT 2011
On Fri, 18 Mar 2011 12:31:23 -0400, Andrei Alexandrescu
<SeeWebsiteForEmail at erdani.org> wrote:
> On 3/18/11 11:07 AM, Steven Schveighoffer wrote:
>> On Fri, 18 Mar 2011 11:35:22 -0400, spir <denis.spir at gmail.com> wrote:
>>
>>> On 03/18/2011 01:37 PM, Steven Schveighoffer wrote:
>>>> This is a good example of why it's difficult to decide what "user
>>>> input" is.
>>>> One could consider that the 'user' in this case is the developer
>>>> using the
>>>> library, but I don't think that's the right choice.
>>>>
>>>> I'd say it's a bug, this is clearly a contract, since the data being
>>>> passed
>>>> into the ctor can easily not be user input (i.e. it's most likely two
>>>> literals
>>>> that will never depend on a user). If it is user input, the caller of
>>>> the ctor
>>>> should enforce the user input before passing it to iota.
>>>
>>> This is indeed a difficult topic. I'm a bit bluffed when reading
>>> people confidently asserting apparently clear positions about the use
>>> of enforce vs assert vs contracts and such, or whether such checks
>>> should or not stay or not in various distribution builds (mainly
>>> -release).
>>> I can see at least 5 cases, and am far to be sure what the proper tool
>>> is in every case, and in which builds it should stay. In each case,
>>> there is potential "wrong" input; but note the variety of cases does
>>> seems orthogonal (lol) to what kind of harm it may cause:
>>>
>>> * colleague: my code is called by code from the same app (same dev
>>> team); typically, wrong input logically "cannot" happen
>>> * friend: my code is called by code designed to cooperate with it;
>>> there is a kind of moral contract
>>> In both cases, wrong input reveals a bug; but in the first case, it's
>>> my own (team's) bug. I guess, but am not sure, these cases are good
>>> candidates for asserts (or contracts?), excluded from release build.
>>>
>>> * lib call: my code is a typical lib; thus, I have zero control on
>>> caller.
>>> I would let the check in release mode, thus use enforce. Or, use
>>> assert if it remains when the *caller* is compiled in debug mode.
>>> There is something unclear here, I guess. Maybe there are two
>>> sub-cases:
>>> ~ the caller logically should be able to prove its args correct
>>> ~ or not
>>
>> See, this is where I feel we have issues. The clear problem with
>> *always* checking is the iota example. One may use iota like this:
>>
>> foreach(i; iota(0, 5))
>>
>> Why should checks in iota remain for iota to prove that 0 is less than
>> 5? It always will be less than 5, and the check is not necessary.
> [snip]
>
> This is the kind of job that the compiler could and should do. Whether
> it's assert and enforce, an inlining pass followed by value range
> propagation should simply eliminate the unnecessary tests.
In this simple case yes, but inlining is not forceable, so you should not
rely on it for optimizing out asserts or enforce. Inlining also only goes
so deep, it doesn't make sense to inline a whole program, so at some
point, you are going to use a function call, even though it can be proven
the data is within range.
From what I can see, we have 3 cases:
1. those where we can prove beyond a doubt that whether a value is valid
is runtime dependent. Those cases should obviously use enforce.
2. Those where we can prove beyond a doubt that whether a value is valid
does not depend on runtime data. Those cases should obviously use assert.
3. You can't prove beyond a doubt where those values come from.
It's case 3 that is the troublesome one, not because it's hard to prove
whether it's case 3 or not, but because the code that knows what the data
could be (the caller) is separate from the code which knows whether its
valid (the callee).
It's also case 3 which is the most common. The most common case is a
library function which wants to validate its input.
So with the tools we have at hand today, which one should be applied to
case 3? My preference is to use assert because then the caller has
control over whether the data is checked or not. If you use enforce, the
caller has no way of saying "no really, I checked that value already!".
Note also that phobos functions may be double checking data ALREADY if
they make calls to each other.
-Steve
More information about the Digitalmars-d
mailing list