July 31, 2014
On Thursday, 31 July 2014 at 06:57:15 UTC, Walter Bright wrote:
> On 7/30/2014 3:39 PM, Joseph Rushton Wakeling via Digitalmars-d wrote:
>> My take is that, for this reason, these should be asserts and not enforce() statements.  What are your thoughts on the matter?
>
> An excellent question.
>
> First, note that enforce() returns a recoverable exception, and assert() a non-recoverable error.
>
> Logically, this means that enforce() is for scrubbing input, and assert() is for detecting program bugs. I'm pretty brutal in asserting (!) that program bugs are fatal, non-recoverable errors. I've been fighting this battle for decades, i.e. repeated proposals by well-meaning programmers who believe their programs can safely recover from an unknown, invalid state.

It may be worth drawing a distinction between serial programs
(ie. a typical desktop app) and parallel programs (ie. server
code).  For example, in a server program, a logic error caused by
one particular type of request often doesn't invalidate the state
of the entire process.  More often, it just prevents further
processing of that one request.  Much like how an error in one
thread of a multithreaded program with no shared data does not
corrupt the state of the entire system.

In short, what one terms a "process" is really somewhat fluid.
What is the distinction between a multi-threaded application
without sharing and multiple instances of the same process all
running individually?  In Erlang, a "process" is really just a
thread being run by the VM, and each process is effectively a
class instance.  All errors are fatal, but they're only fatal for
that one logical process.  It doesn't take down the whole VM.

Now in a language like D that allows direct memory access, one
could argue that any logic error may theoretically corrupt the
entire program, and I presume this is where you stand.  But more
often in my experience, some artifact of the input data or
environmental factors end up pushing execution through a path
that wasn't adequately tested, and results in a fully recoverable
error (initiated by bad program logic) so long as the error is
detected in a timely manner.  These are issues I want caught and
signaled in the most visible manner possible so the logic error
can be fixed, but I don't always want to immediately halt the
entire process and terminate potentially thousands of entirely
correct in-progress transactions.

Perhaps these are all issues that should be marked by enforce
throwing a ProgramLogicException rather than assert with an an
AssertError, but at that point it's almost bikeshedding.
Thoughts?
July 31, 2014
On 7/31/2014 1:33 PM, David Nadlinger wrote:
> I've had the questionable pleasure of tracking down a couple of related issues
> in LLVM and the LDC codegen, so please take my word for it: Requiring any
> particular behavior such as halting in a case that can be assumed to be
> unreachable is at odds with how the term "unreachable" is used in the wild – at
> least in projects like GCC and LLVM.

For example:

 int foo() {
   while (...) {
       ...
   }
   assert(0);
 }

the compiler needn't issue an error at the end "no return value for foo()" because it can assume it never got there.

I'll rewrite that bit in the spec as it is clearly causing confusion.

July 31, 2014
On Thursday, 31 July 2014 at 21:11:17 UTC, Walter Bright wrote:
> On 7/31/2014 1:52 PM, Sean Kelly wrote:
>> Could you expand on what you consider input?
>
> All state processed by the program that comes from outside the program. That would include:
>
> 1. user input
> 2. the file system
> 3. uninitialized memory
> 4. interprocess shared memory
> 5. anything received from system APIs, device drivers, and DLLs that are not part of the program
> 6. resource availability and exhaustion

So effectively, any factor occurring at runtime.  If I create a
library, it is acceptable to validate function parameters using
assert() because the user of that library knows what the library
expects and should write their code accordingly.  That's fair.
July 31, 2014
Am 31.07.2014 23:21, schrieb Sean Kelly:
> On Thursday, 31 July 2014 at 06:57:15 UTC, Walter Bright wrote:
>> On 7/30/2014 3:39 PM, Joseph Rushton Wakeling via Digitalmars-d wrote:
>>> My take is that, for this reason, these should be asserts and not
>>> enforce() statements.  What are your thoughts on the matter?
>>
>> An excellent question.
>>
>> First, note that enforce() returns a recoverable exception, and
>> assert() a non-recoverable error.
>>
>> Logically, this means that enforce() is for scrubbing input, and
>> assert() is for detecting program bugs. I'm pretty brutal in asserting
>> (!) that program bugs are fatal, non-recoverable errors. I've been
>> fighting this battle for decades, i.e. repeated proposals by
>> well-meaning programmers who believe their programs can safely recover
>> from an unknown, invalid state.
>
> It may be worth drawing a distinction between serial programs
> (ie. a typical desktop app) and parallel programs (ie. server
> code).  For example, in a server program, a logic error caused by
> one particular type of request often doesn't invalidate the state
> of the entire process.  More often, it just prevents further
> processing of that one request.  Much like how an error in one
> thread of a multithreaded program with no shared data does not
> corrupt the state of the entire system.
>
> In short, what one terms a "process" is really somewhat fluid.
> What is the distinction between a multi-threaded application
> without sharing and multiple instances of the same process all
> running individually?  In Erlang, a "process" is really just a
> thread being run by the VM, and each process is effectively a
> class instance.  All errors are fatal, but they're only fatal for
> that one logical process.  It doesn't take down the whole VM.
>
> Now in a language like D that allows direct memory access, one
> could argue that any logic error may theoretically corrupt the
> entire program, and I presume this is where you stand.  But more
> often in my experience, some artifact of the input data or
> environmental factors end up pushing execution through a path
> that wasn't adequately tested, and results in a fully recoverable
> error (initiated by bad program logic) so long as the error is
> detected in a timely manner.  These are issues I want caught and
> signaled in the most visible manner possible so the logic error
> can be fixed, but I don't always want to immediately halt the
> entire process and terminate potentially thousands of entirely
> correct in-progress transactions.
>
> Perhaps these are all issues that should be marked by enforce
> throwing a ProgramLogicException rather than assert with an an
> AssertError, but at that point it's almost bikeshedding.
> Thoughts?

I agree.
Also: During development I'd be fine with this terminating the program (ideally dumping the core) so the error is immediately noticed, but in release mod I'm not, so a construct that halts in debug mode and throws an exception or maybe just returns false (or one construct for each of the cases) in release mode would be helpful.

Cheers,
Daniel
July 31, 2014
On 07/31/2014 11:01 PM, ponce wrote:
> Could you expand on what you consider input?  For example, if a
> function has an "in" contract that validates input parameters, is
> the determination that a parameter is invalid a program bug or
> simply invalid input? ...
>

The assertions in an 'in' contracts are obligations at the call site.

I.e., the code:

void baz(int x){
    assert(x>2);
    // ...
}

is buggy.

The code

void foo(int x)in{ assert(x>2); }body{ assert(x>2); }

is correct.

The code:

void bar(int x){ foo(x); }

is buggy.

The code

void bar(int x){
    enforce(x>2);
    foo(x);
}

is fine.


> This also puzzles me. There is the point where the two types of errors
> blend to the point of being uncomfortable.
>
> Eg: a program generates files in X format and can also read them with a
> X parser. Its X parser will only ever read output generated by itself.
> Should input errors in X parser be checked with assert or exceptions?

Use 'assert' for checking things which you expect to be true. But don't be fooled, besides having some loosely checked code documentation, the main reason to write down assertions is that they will occasionally fail and tell you something interesting which you didn't know yet about your program (as it was nicely formulated somewhere else in this thread.) This makes assertions seem a little schizophrenic at times, but after all, checking those assertions, which you _expect_, by definition, to be no-ops anyway, may be too expensive for you and then they can be disabled. I'd check what are the actual performance gains before doing this though. You can also control assertions in a more fine-grained way by guarding them with version statements.

Furthermore, use 'assert' as well in _in_ contracts, and think about them being checked in the context of the caller as an obligation instead of as being checked in your own function.

Use 'enforce' for things which you expect might be false sometimes and that you may want to handle as an exception in this case.

I wouldn't think about 'enforce' as 'not checking program bugs' too hard though. Maybe the bug is in code which does not actually expect the exceptional path to be taken.

The difference between

void foo1(int x)in{ assert(x>2); }body{ ... }

and

void foo2(int x){
    enforce(x>2);
}

Is that 'foo2' reliably throws an exception if the contents of x are not greater than 2; this is part of it's behaviour and would be part of its documentation, while 'foo1' just states in its documentation that it will do a certain thing _provided_ x is greater than 2, and that its behaviour is left unspecified otherwise.


Or that's what I would say anyway if there wasn't talk about turning assertions into assumptions in -release. If nothing changes, always use version(assert) to guard your assert statements unless you want their failures to be undefined behaviour in -release mode.

July 31, 2014
On 07/31/2014 11:39 PM, Timon Gehr wrote:
> it's behaviour

gah.
July 31, 2014
On 7/31/2014 10:40 AM, Daniel Gibson wrote:
> It's a major PITA to debug problems that only happen in release builds.

Debugging optimized code was a well known problem even back in the 70's. Nobody has solved it, and nobody wants unoptimized code.

July 31, 2014
On 7/31/2014 10:57 AM, John Colvin wrote:
> I believe gdc has the full suite of gcc optimiser flags available. You don't
> need to just slap -O3 on and then complain about the changes it makes, you can
> choose with a reasonable amount of precision which optimisations you do/don't
> want done.

DMC++ has similar switches, but nobody uses them, and they are pretty much useless anyway, because:

1. you need to be a compiler guy to know what they mean - very few people know what "code hoisting" is

2. it's the combination of optimizations that produces results - they are not independent of each other

This is why I didn't include such switches in DMD.
July 31, 2014
On 7/31/2014 10:26 AM, Daniel Murphy wrote:
> No you don't, no you can't.  You are using an optimizer because writing it in
> the perfectly precise, perfectly fast way makes your code unmaintainable. You
> want the optimizer to delete all those never-read initializations, drop all
> those temporary variables, and turn your multiplies into shifts and additions.
>
> If you didn't, you wouldn't be using an optimizer.

Even "unoptimized" code does optimizations.

If you really want unoptimized code, what you write is what you get, the inline assembler beckons!
July 31, 2014
On 07/31/2014 11:25 PM, Walter Bright wrote:
>
> I'll rewrite that bit in the spec as it is clearly causing confusion.

A wording that avoids all those issues would be something like: "'assert(0)' never returns and hence terminates the basic block it occurs in."