January 04, 2020
On 1/4/2020 1:07 PM, Adam D. Ruppe wrote:
> On Saturday, 4 January 2020 at 20:53:34 UTC, Dennis wrote:
>> Walter's current plan seems to be allowing the `throw` keyword to be used as an attribute.
>>
>> https://github.com/dlang/DIPs/pull/167/files
> 
> that looks good to me btw. I'm against the default change but that DIP appears to do what I'd want - the negating behavior is also well done.

Your comment is exactly why I'm doing this as two DIPs instead of one :-)
January 04, 2020
On Saturday, 4 January 2020 at 21:38:53 UTC, Walter Bright wrote:
>
> The first step is to add `throw` as a function attribute,
>
> https://github.com/dlang/DIPs/pull/167
>
> The next step will be to make nothrow the default. I have not prepared a DIP for that yet, but will.
>
> The short rationale is that exceptions being a "pay only if you use them" is a complete fraud. They're expensive to support, meaning performance programs use other ways of signalling errors and use nothrow.

While reusing throw might be convenient, it also makes the code less greppable. Kind of like static in C has three different uses.
January 04, 2020
On Saturday, 4 January 2020 at 21:38:53 UTC, Walter Bright wrote:
> The short rationale is that exceptions being a "pay only if you use them" is a complete fraud. They're expensive to support, meaning performance programs use other ways of signalling errors and use nothrow.

There's more to code than performance. D is an all-purpose language.

But anyway, D still allows throwing from nothrow, but only if it is the Error class. I presume that would not change.

Would you make throw Error instead just maybe try to print the message to stderr then abort? That's not a bad idea, and is compatible with D's spec as it stands now. (the print to stderr could be problematic in some contexts but we'd just do a runtime function override in that case)

Then you wouldn't need any EH framing around it. This could be done without changing the default too.


But my worry on nothrow by default in general is then more D code will just become fragile since other error methods have historically meant they simply get ignored and not even reported.... barring something like those newfangled type based systems that's probably what we'd become too.

BTW I have some other potential uses for those newfangled type based systems. I wouldn't object to getting some of those pieces in too - we can already do much of it.

If 1) there was a way to say "ignoring the return value of this function is an error" (but that cannot apply everywhere!) and 2) be able to return from the caller from a callee (think of C macros that like `if(!SUCCEEDED(hr)) return hr` type of thing), I think we could do some real magic and have a serious alternative to exceptions. And do other useful things too.

My suggestion would be to do those before attempting nothrow by default too. (then we can still vote down the default change and be left with new tools for people who want to use them :P )


January 04, 2020
On Saturday, 4 January 2020 at 22:01:48 UTC, JN wrote:
> On Saturday, 4 January 2020 at 21:38:53 UTC, Walter Bright wrote:
>>
>> The first step is to add `throw` as a function attribute,
>>
>> https://github.com/dlang/DIPs/pull/167
>>
>> The next step will be to make nothrow the default. I have not prepared a DIP for that yet, but will.
>>
>> The short rationale is that exceptions being a "pay only if you use them" is a complete fraud. They're expensive to support, meaning performance programs use other ways of signalling errors and use nothrow.
>
> While reusing throw might be convenient, it also makes the code less greppable. Kind of like static in C has three different uses.

I agree, we could use "throws" instead of "throw".
January 05, 2020
On Saturday, 4 January 2020 at 21:07:03 UTC, Adam D. Ruppe wrote:
> On Saturday, 4 January 2020 at 20:53:34 UTC, Dennis wrote:
>> Walter's current plan seems to be allowing the `throw` keyword to be used as an attribute.
>>
>> https://github.com/dlang/DIPs/pull/167/files
>
> that looks good to me btw. I'm against the default change but that DIP appears to do what I'd want - the negating behavior is also well done.
>
>> That still leaves the question how @nogc and pure will be negated, and how to specify the default attributes for a module without affecting template functions and functions with `auto` return-type.
>
> lol we could always use existing keywords like crazy:
>
> pure!false
> @nogc!true
> nothrow!default
> virtual!false
>
> or something similar.
>
> gotta love the double negatives but meh. throw already being a keyword is convenient. pure is already positive so convenient. just @nogc, the odd one out.
>
> but like regardless i wouldn't oppose such a thing.
>
> i also wouldn't oppose impure and @gc keywords added.
>
>
> I also wouldn't oppose templates simply not being affected by scope-level keyword attributes. That might be a winner actually.... maybe. If you want it to apply to the template, you'd just have to explicitly write it again. They get special case because of the inference rule.
>
> Could also mean
>
> @safe class Foo {
>    final void my_template()(); // actually inferred, despite outer @safe
> }
>
>
> idk, just throwing out ideas. To be honest I'd take just about *anything* that actually happens now over some other idea that we have to wait several years for again.

D should make attribute keywords consistent by adding inversed value using "!". This will help to reduce the number of attributes and a way to inverse the block setting. For any attributes that support block in form of ...":" or ...{...}, it should apply to all functions & function/delegate alias.
@trusted is a dangerous construct and should not be allowed to be use in block construct.

pure
!pure
@gc
@!gc => as current @nogc and depreciate @nogc
throw
!throw => as current nothrow and depreciate nothrow
@safe
@!safe => as current @system and depreciate @system

January 05, 2020
On Sunday, 5 January 2020 at 04:37:57 UTC, apz28 wrote:
> pure
> !pure
> @gc
> @!gc => as current @nogc and depreciate @nogc
> throw
> !throw => as current nothrow and depreciate nothrow
> @safe
> @!safe => as current @system and depreciate @system

I would like to have everything with @: @nothrow, @pure, @nopure (or !@throw, !@pure but I like @nothrow and @nopure more). And one my even think about @const to avoid the ambiguity of "const int f()".
January 05, 2020
On 1/4/2020 3:06 PM, Adam D. Ruppe wrote:
> On Saturday, 4 January 2020 at 21:38:53 UTC, Walter Bright wrote:
>> The short rationale is that exceptions being a "pay only if you use them" is a complete fraud. They're expensive to support, meaning performance programs use other ways of signalling errors and use nothrow.
> 
> There's more to code than performance. D is an all-purpose language.
> 
> But anyway, D still allows throwing from nothrow, but only if it is the Error class. I presume that would not change.

I've been thinking about getting rid of Error entirely, and instead using a scheme that calls a user-supplied function when things go that wrong. (Error is non-recoverable.)


> But my worry on nothrow by default in general is then more D code will just become fragile since other error methods have historically meant they simply get ignored and not even reported.... barring something like those newfangled type based systems that's probably what we'd become too.

It shouldn't become fragile, because the compiler will reject nothrow code on the call stack if an exception is thrown.
January 05, 2020
Am Sat, 04 Jan 2020 13:38:53 -0800 schrieb Walter Bright:

> 
> The short rationale is that exceptions being a "pay only if you use them" is a complete fraud. They're expensive to support, meaning performance programs use other ways of signalling errors and use nothrow.

I totally agree to that (memory overhead, TypeInfo, support code). In addition, this overhead means that exceptions are unlikely to be supported in embedded systems, which will lead to a language ecosystem split (this effectively happened to C++).

But I'm not sure if that's a good rationale for nothrow by default. We already tell users to use exceptions only for "exceptional cases" and use other error handling mechanism for common error paths. With this change, we'd force users even stronger to use other error handling methods. But those don't have language support and we're back to C times, where users forget to check error codes, ...


Herb Sutter came to the same conclusion recently and proposed an alternative exception implementation for C++. If you didn't see this already Walter, it well worth to have a look:

https://www.youtube.com/watch?v=os7cqJ5qlzo https://www.youtube.com/watch?v=ARYP83yNAWk


I'll summarize the idea here, as far as I remember and adapted to D terminology / context:

Rationale: There are two common ways to handle errors: Backtrace/ exception based and error code based. Both have drawbacks (exceptions: performance, memory / implementation overhead, not supported in embedded systems) (error codes: not "bubbling up" the call stack automatically, no way to force user to check, so might accidentially ignore errors).

Solution:
1) Implement exceptions like return codes: Each function which can throw
   returns a union ValueOrError{T result; Error err}. The discriminator
   bit is stored as a CPU flag register which is not  used across
   function calls (e.g. the OVERFLOW status bit). Error is a two-word
   error code: Error {size_t code; size_t context}. This explains the
   ABI, for now nothing of this is user-visible.

   Whenever a call to a function which might throw occurs and there's a
   try-catch handler, insert this code after the call:
   if (OVERFLOW == 1)
       goto catch_handler;
   If there's no catch handler installed and a function might throw:
   if (OVERFLOW == 1)
       return;
   The return value is already in the return registers. Therefore
   propagating error codes upwards is cheap.

   The benefits now are simple: Implementation code / memory overhead is
   almost zero, no TypeInfo required, trivial to implement on embedded
   systems, .... At the same time, exceptions bubble up properly so they
   can't be accidentially unhandled. The runtime overhead in the success
   case is obviously higher than for some exception systems, but it's
   only a simple conditional jump. It is cheaper than manual error codes,
   as we reuse the same registers for return value and error code,
   benefitting register allocation. Error propagation also uses the same
   registers in all functions and is therefore very efficient.


   For D / legacy exception support, you just store
   code=LEGACY_EXCEPTION, context=Exception pointer. To allocate the
   error codes in a distributed way and to check
   for different error types, we can simply store dummy values in the
   data segment to get unique addresses:
   ubyte OutOfMemoryError; ... ; if (ret.code == &OutOfMemoryError)...

2) (Optional): Herb arguest that because of throw ... it is easy to spot
   where an exception originates, but it's more difficult to find where
   an exception was propagated. As a solution,
   whenever calling a throws function, the calls should be preceeded by
   throw:
   auto value = throw myThrowingFunction();
   Here throw does essentially this: If myThrowingFunction threw, rethrow
   the exception. Otherwise return the return value.

3) (C++ specific): Error codes instead of exceptions, "Type based Errors":
   I don't really know what is meant by this "Type-Based Errors"
   terminology, seems to be a C++ marketing thing ("we do everything with
   types now")... The important takeaway is to not allocate complex
    exception objects. Use the error code and context value. If really
   more context than one word is necessary, it's still possible to stuff
   a pointer into context.

4) (D-specific): Not mentioned in the C++ proposal, but some interesting
   extension: A more local error handling solution:
   In D we have try/catch if we want to handle errors from multiple
   functions calls and scope(failure) to handle all errors in a function.
   But fine-grain error handling is annoying:
   -----------------
   try
       foo();
   catch(Exception e)
   {
       switch(e)
       {
           case DNS_ERROR:
               writeln("Dns erorr");
               retry();
           case ...
       }
   }
   // success code here
   -----------------

   quite some overhead here. Traditional error codes are better here:
   -----------------
   T ret;
   switch(ret = foo())
   {
       case ...:
           return;
   }
   // Success here
   -----------------

   Maybe we could formalize this: We can expose the ValueOrError union to
   the user. foo() then returns ValueOrError!T and the 'throw' in 2)
   could simply check for errors, otherwise return ValueOrError!(T).value.
   We could then add an opCast(bool) overload to ValueOrError to make
   if (value) work, add a function to check/abort and convert
   ValueOrError.getValue and overload the switch statement on this:
   -----------------
   auto res = foo();
   switch(res.error)
   {
        case DNS_ERROR:
            return;
        default: // Other error
            return;
   }
   // With flow-typing we'd know that ValueOrError is no error here.
   Without, we have to explicitly convert the type somehow:

   T value;
   // Switch magically overloaded on error / value tuple
   switch ((value, error) = foo())
   {
       writeln(error);
       return;
   }
   writeln(value);
   -----------------

   However, with these ideas in 4), we'd have to force the user somehow
   to check the error bit and make the value only accessible after the
   check.


Caveats:
* I have not thought about how exception chaining fits into all this.
* These exceptions do not naturally propagate through foreign language
  interfaces, although I think we don't guarantee this in D right now
  either.


If there's any real interest in this, I'd be happy to write a proper DIP(s) for it (I think 1, 2 and 4 are all independent DIPs). If C++ really gets this, we might have to support it anyway for C++ interop.


PS: Back to the original question: nothrow by default: With 2) every function which might throw and where errors are not handled locally needs to have throw in front of the function call. Obviously, we don't want to have too many of these, so nothrow functions should be the common case. And the common case should be the default, so nothrow by default also makes sense in this context.

-- 
Johannes
January 05, 2020
On Sunday, 5 January 2020 at 10:32:23 UTC, Johannes Pfau wrote:
>    The benefits now are simple: Implementation code / memory overhead is
>    almost zero, no TypeInfo required, trivial to implement on embedded
>    systems, .... At the same time, exceptions bubble up properly so they
>    can't be accidentially unhandled. The runtime overhead in the success
>    case is obviously higher than for some exception systems, but it's
>    only a simple conditional jump. It is cheaper than manual error codes,
>    as we reuse the same registers for return value and error code,
>    benefitting register allocation. Error propagation also uses the same
>    registers in all functions and is therefore very efficient.
>

Consulting Agner Fox's microarchitecture manual, current Intel CPUs still take a performance hit when encountering code with a high density of conditional branches. They are sensitive to the number of branch instructions per cache line. I've seen GCC pad code with NOPs for that reason. AMD's branch predictor, on the other hand, is described as perceptron that fares better with repeated patterns of branches and isn't tied to the cache. Branch mispredictions still hurt a lot (~20 cycles). All microcontrollers I'm familiar stall their pipeline on every branch if they're pipelined at all.

Moving error handling overhead back into the common/fast code path is a step backwards in those cases where performance matters. Desktops and servers can absolutely afford to have the data overhead associated with exceptions and they benefit the most from out of band error handling.

It would be nice to have choice here.

> 2) (Optional): Herb arguest that because of throw ... it is easy to spot
>    where an exception originates, but it's more difficult to find where
>    an exception was propagated. As a solution,
>    whenever calling a throws function, the calls should be preceeded by
>    throw:
>    auto value = throw myThrowingFunction();
>    Here throw does essentially this: If myThrowingFunction threw, rethrow
>    the exception. Otherwise return the return value.
>

I hope that this doesn't require code to have a throw keyword in every other line. Imagine outer functions of some algorithm where the inner functions have a lot of opportunity to fail:

auto result1 = throw step1();
auto result2 = throw step2(result1);
// etc...

The value of the keyword decreases rapidly with the number of occurrences.

> 3) (C++ specific): Error codes instead of exceptions, "Type based Errors":
>    I don't really know what is meant by this "Type-Based Errors"
>    terminology, seems to be a C++ marketing thing ("we do everything with
>    types now")... The important takeaway is to not allocate complex
>     exception objects. Use the error code and context value. If really
>    more context than one word is necessary, it's still possible to stuff
>    a pointer into context.
>

There's recently been a talk about error types in C++ and their evolution:

https://www.youtube.com/watch?v=coBz_CQ1tJ8&

As with everything in C++, there's a lot of complexity.

>
> Caveats:
> * I have not thought about how exception chaining fits into all this.

This would require an exception to be allocated that starts the chain. The error return value could then turn into a pointer to that value. That's similar to how the C++ proposals return exceptions in error value returning functions.

> * These exceptions do not naturally propagate through foreign language
>   interfaces, although I think we don't guarantee this in D right now
>   either.

D exceptions and C++ exception implementations are currently quite incompatible, but the documentation still states that that is an eventual goal of D:

https://dlang.org/spec/cpp_interface.html#exception-handling

January 05, 2020
On Sunday, 5 January 2020 at 10:32:23 UTC, Johannes Pfau wrote:
> I totally agree to that (memory overhead, TypeInfo, support code). In addition, this overhead means that exceptions are unlikely to be supported in embedded systems, which will lead to a language ecosystem split (this effectively happened to C++).

Not only the overhead, but in real-time interrupts have to be 100% sure that no code triggers a system call or ends up waiting for a lock to be released. Also in applications (e.g. audio drivers).

Seems to me that static analysis could be used for this, the same way @nogc works.  I think D should look at something more comprehensive regarding managing resources instead of these  tweaks that have very little positive impact.


> Rationale: There are two common ways to handle errors: Backtrace/ exception based and error code based. Both have drawbacks (exceptions: performance, memory / implementation overhead, not supported in embedded systems) (error codes: not "bubbling up" the call stack automatically, no way to force user to check, so might accidentially ignore errors).

Yes, but back-tracing with error codes can be slow.

RAII can be slow.

You can have exceptions in real time systems (setjmp), basically just reload saved register values that sets the stack pointer and program counter to a consistent state.

So an exception (set jump) can be  much faster than back-tracing if you have no RAII on the call stack and all allocations are done either on the stack and all other resources are bound to an Arena-allocator/manager at the landing pad.

What makes C++-style exceptions slow is the unwind library's cross-language design and the surrounding RAII philosophy... Exceptions, as a concept, are not necessarily slow. If you can just set the stack pointer and jump to the landing pad then you have something much faster than returning/unwinding down the call stack for deep call-trees.

But since D should be able to call C++ code that won't work, and nothrow by default will not help with C++. So to get anywhere you first have to figure out how to interact with C++ code. If you call C++ code then you cannot assume that it won't throw, unless it is marked as "noexcept" and even if it is noexcept it still can have landing pads. So you cannot bypass the unwinding library... Am I right?

In terms of supporting generic programming it is better to have "possibly throws" everywhere.