D is dead

Walter Bright newshound2 at digitalmars.com
Thu Aug 23 23:27:51 UTC 2018


On 8/23/2018 3:12 PM, David Nadlinger wrote:
> On Thursday, 23 August 2018 at 21:31:41 UTC, Walter Bright wrote:
>> My personal opinion is that constructors that throw are an execrable 
>> programming practice, and I've wanted to ban them. (Andrei, while sympathetic 
>> to the idea, felt that too many people relied on it.) I won't allow throwing 
>> constructors in dmd or any software I have authority over.
> 
> Throwing constructors are fundamental for making RAII work in a composable fashion.

I understand my opinions on this diverge from the conventional wisdom.


> If constructors are not allowed to throw and you want to avoid manually creating 
> a "uninitialized" state – which is error-prone and defeats much of the point of 
> an RAII strategy –, all dependencies need to be injected externally, that is, 
> constructed independently and then passed into the constructor. Sometimes, 
> inversion of control is of course the right call – cf. the hype around DI –, but 
> sometimes you'd rather cleanly abstract the implementation details away.
> 
> Banning them from the language only pushes the complexity of handling 
> semi-constructed objects into ad-hoc user code solutions, which I'd argue is 
> worse in terms of usability and potential for bugs.
> 
> I suppose you view this as advantageous because you place more weight on the 
> language not having to explicitly deal with this scenario in the text of the 
> specification?

It's easy to specify. That's not an issue at all. It's also easy to implement - 
where my PR for it failed, was it broke existing code that should never have 
compiled anyway (for example, a nothrow constructor would then call a throwing 
destructor, or an @safe constructor now would call an unsafe destructor). 
Dealing with this likely means a compiler switch so an upgrade path is easier.

Let's deal first with the easy case - throwing destructors. The problem is that 
unwinding the stack to deal with the exception means calling destructors. You 
then have the infamous "double fault exception", and C++ deals with it by 
terminating the program, which is hardly useful.

D deals with it via "chained exceptions", which is terrifyingly difficult to 
understand. If you believe it is understandable, just try to understand the 
various devious test cases in the test suite. I regard D's chained exceptions as 
an utter failure.

Back to throwing constructors.

1) They are expensive, adding considerable hidden bloat in the form of finally 
blocks, one for each constructing field. These unwinding frames defeat 
optimization. The concept of "zero-cost exception handling" is a bad joke. (Even 
Chandler Carruth stated that the LLVM basically gives up trying to optimize in 
the presence of exception handlers.) Herb Sutter has a recent paper out 
proposing an alternative, completely different, error handling scheme for C++ 
because of this issue.

2) The presence of constructors that throw makes code hard to reason about. (I 
concede that maybe that's just me.) I like looking at code and knowing the 
construction is guaranteed to succeed. Somehow, I've been able to use C++ for 
decades without needing throwing constructors. Let's take the canonical example, 
a mutex:

   {
     Mutex a; // acquires a mutex, throws if it fails
     ... locked code ...
   } // mutex is released

My suggestion:

   {
     Mutex a; // creates a mutex
     a.acquire(); // does the obvious thing
     ... locked code ...
   } // mutex is released

It's still RAII, as the destructor checks to see if the Mutex is required, and 
if so, releases it.

You might argue "my code cannot handle that extra check in the destructor." Fair 
point, but stack that against the cost of the EH bloat in the constructor, and 
(for me) the inherently unintuitive nature of a constructor that tries to do far 
too much.

3) Much of the utility of throwing constructors in C++ comes from "what if the 
constructor fails to allocate memory". In D, out of memory errors are fatal, no 
recovery is necessary. That pushes any requirement for throwing constructors to 
the fringes in the first place.


More information about the Digitalmars-d mailing list