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?

Reply via email to