D is dead

David Nadlinger code at klickverbot.at
Fri Aug 24 03:53:38 UTC 2018


On Thursday, 23 August 2018 at 23:27:51 UTC, Walter Bright wrote:
> 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 don't think that assessment is accurate. Yes, I ported EH to a 
few new targets and wrote the first correct implementation of 
exception chaining for LDC, so I'm probably a couple of standard 
deviations off the average D user in that regard. But said 
average D user doesn't care about most of the nuances of this 
problem, like the details of implementing exception chaining 
without allocating too much, or which exceptions take precedence 
in various intentionally twisted test cases.

What they do care about is that the fact that an error has 
occurred is propagated sensibly in all cases without requiring 
extra attention, and that information as to the origin is not 
lost (hence chaining rather than just replacement). Heck, the 
fact that we still don't have default backtrace handlers that 
consistently work on all platforms is probably a much bigger 
problem than the minutiae of exception chaining behaviour.

All this is not to say that nothrow constructors aren't a good 
idea, though.

———

> 1) They are expensive, adding considerable hidden bloat in the 
> form of finally blocks, one for each constructing field. These 
> unwinding frames defeat optimization. […]

This cost is inherent to the problem, at least as long as 
exceptions are used to represent the error conditions in question 
in the first place. Whether the potentially throwing operations 
are performed in the constructor or in another method, either way 
the object will need to be destructed and all preceding 
initialisation steps undone when an error occurs.

> 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.

This might be just you indeed. How are constructors different 
than other method calls in that regard? (Granted, constructors 
can be called implicitly in C++.)

> Somehow, I've been able to use C++ for decades without needing 
> throwing constructors.

How much of what would be considered "modern" C++ code have you 
written in that time, as opposed to C-with-classes style?

> It [having a separate initialisation method]'s still RAII

One might wonder about the acronym then, but whether your example 
should be called RAII is an entirely different debate, and one 
I'm not particularly interested in (I've always thought something 
like "Resource Release Is Destruction" or just "Scope-based 
resource management" would be a better name anyway).

> You might argue "my code cannot handle that extra check in the 
> destructor."

It's not just one extra check in the destructor. It's an extra 
check in every member function, to throw an exception if called 
on an object that has not been initialised.

You might argue that these should be errors/assertions instead, 
and hence are not as expensive. True, but this points to another 
problem: Splitting up the initialisation procedure invites a 
whole class of bugs that would otherwise be syntactically 
impossible to write.

There is considerable theoretical and practical beauty to the 
idea that as an object is accessible, it is in a valid state. 
(Linear/affine types, cf. Rust, show the power of this notion 
extended to ownership.) RAII in the conventional sense 
(acquisition in the constructor) effectively provides a way to 
encode 1 bit of typestate. By blurring the line between object 
initialisation and the phase where it is fully initialised, that 
descriptive power is lost.

> […] (for me) the inherently unintuitive nature of a constructor 
> that tries to do far too much.

I suppose our opinions just differ here then. To me, this is 
exactly what I intuitively expect a constructor to do: Create an 
instance of an object in a normal state. Drawing the line 
anywhere else, for example (implicitly) allocating memory but not 
initialising it to a fully usable state, seems artificial to me.

> 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.

This is unrelated to the discussion at hand, but 
std.experimental.allocator does allow handle allocation failure, 
and with good reason: It doesn't make sense to abort the program 
when a fixed-size local cache or a static global object pool 
(common in embedded programming) is exhausted.

  — David


More information about the Digitalmars-d mailing list