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