On Tuesday, 5 January 2021 at 21:46:46 UTC, H. S. Teoh wrote:
4) The universal error type contains two fields: a type field
and a context field.
a) The type field is an ID unique to every thrown exception
--
uniqueness can be guaranteed by making this a pointer to
some static
global object that the compiler implicitly inserts per throw
statement, so it will be unique even across shared
libraries. The
catch block can use this field to determine what the error
was, or
it can just call some standard function to turn this into a
string
message, print it and abort.
Why it must be unique? Doesn't it suffice to return the typeid
here?
b) The context field contains exception-specific data that
gives
more information about the nature of the specific instance
of the
error that occurred, e.g., an integer value, or a pointer
to a
string description or block of additional information about
the
error (set by the thrower), or even a pointer to a
dynamically-allocated exception object if the user wishes
to use
traditional polymorphic exceptions.
Okay, but in 99% you need dynamically allocated objects because
the context is most of the time simply unknown.
But yes, in specific cases a simple error code suffice, but even
then it would be better to be aware that an error code is
returned instead of a runtime object. It sucks to me to box over
the context pointer/value to find out if it is an error code or
not when I only want an error code.
c) The universal error type is constrained to have trivial
move
semantics, i.e., propagating it up the call stack is as
simple as
blitting the bytes over. (Any object(s) it points to need
not be
thus constrained, though.)
The value semantics of the universal error type ensures that
there is no overhead in propagating it up the call stack. The
universality of the universal error type allows it to represent
errors of any kind without needing runtime polymorphism, thus
eliminating the overhead the current exception implementation
incurs.
So it seems the universal error type just tells me if there is or
isn't error and checking for it is just a bitflip?
The context field, however, still allows runtime polymorphism
to be supported, should the user wish to.
Which in most of the cases will be required.
The addition of the universal error type to return value is
automated by the compiler, and the user need not worry about
it. The usual try/catch syntax can be built on top of it.
Of course, this was proposed for C++, so a D implementation
will probably be somewhat different. But the underlying thrust
is: exceptions become value types by default, thus eliminating
most of the overhead associated with the current exception
implementation.
I didn't know exactly how this is implemented in D, but class
objects are passed as simple pointer and pointers are likewise
value types.
Using value types itself doesn't guarantee anything about
performance, because the context field of an exception can be
anything you need some kind of boxing involving runtime
polymorphism anyway.
Stack unwinding is replaced by normal function return
mechanisms, which is much more optimizer-friendly.
I heard that all the time, but why is that true?
This also lets us support exceptions in @nogc code.
Okay, this would be optionally great. However, if we insert the
context pointer into a List we may get a problem of cyclicity.
There is no need for a cascade of updates if you do it right.
As I hinted at above, this enumeration does not have to be a
literal enumeration from 0 to N; the only thing required is
that it is unique *within the context of a running program*. A
pointer to a static global suffices to serve such a role: it is
guaranteed to be unique in the program's address space, and it
fits in a size_t. The actual value may differ across different
executions, but that's not a problem: any references to the ID
from user code is resolved by the runtime dynamic linker -- as
it already does for pointers to global objects. This also
takes care of any shared libraries or dynamically loaded .so's
or DLLs.
What means unique, why is it important? Type ids aren't unique to
distinguish exceptions and I don't know why we need this
requirement.
The point in Rust or Java was to limit the plurality of error
types a function call receive, but this is exactly the point
where idiomatic and productive development differs. Assumptions
change and there you are.
I've said this before, that the complaints about the current
exception handling mechanism is really an issue of how it's
implemented, rather than the concept of exceptions itself.
Okay, I think this is definitely debatable.
If we implement Sutter's proposal, or something similar
suitably adapted to D, it would eliminate the runtime overhead,
solve the @nogc exceptions issue, and still support traditional
polymorphic exception objects that some people still want.
If we don't care of the exception type nor on the kind of message
of an exception did we have either runtime overhead excluding
unwinding?
I refer here to the kind of exception as entity. Does a class
object really require more runtime polymorphism than a tagged
union?
The other point is how to unify the same frontend (try catch)
with different backends (nonlocal jumps+unwinding vs value type
errors implicitly in return types).
You can use Sutter's proposal in your whole project, but what is
with libraries expecting the other kind of error handling backend.
Did we provide an implicit conversion from one backend to another
either by turning an error object into an exception or vice versa?