On Wednesday, 6 January 2021 at 21:27:59 UTC, H. S. Teoh wrote:
It must be unique because different functions may return different sets of error codes. If these sets overlap, then once the error propagates up the call stack it becomes ambiguous which error it is.

Contrived example:

        enum FuncAError { fileNotFound = 1, ioError = 2 }
        enum FuncBError { outOfMem = 1, networkError = 2 }

        int funcA() { throw FuncAError.fileNotFound; }
        int funcB() { throw FuncBError.outOfMem; }

        void main() {
                try {
                        funcA();
                        funcB();
                } catch (Error e) {
                        // cannot distinguish between FuncAError and
                        // FuncBError
                }
        }


Thanks, reminds on swift error types which are enum cases.
So the type is the pointer to the enum or something which describes the enum uniquely and the context is the enum value, or does the context describe where to find the enum value in the statically allocated object.

Using the typeid is no good because: (1) typeid in D is a

Sorry, I misspelled it, I meant the internal id in which type is turned to by the compiler, not the RTTI structure of a type at runtime.

If you're in @nogc code, you can point to a statically-allocated block that the throwing code updates with relevant information about the error, e.g., a struct that contains further details about the error

But the amount of information for an error can't be statically known. So we can't pre-allocate it via a statically allocated block, we need some kind of runtime polymorphism here to know all the fields considered.

You don't need to box anything. The unique type ID already tells you what type the context is, whether it's integer or pointer and what the type of the latter is.

The question is how can a type id as integer value do that, is there any mask to retrieve this kind of information from the type id field, e.g. the first three bits say something about the context data type or did we use some kind of log2n hashing of the typeid to retrieve that kind of information.


When you `throw` something, this is what is returned from the function. To propagate it, you just return it, using the usual function return mechanisms. It's "zero-cost" because it the cost is exactly the same as normal returns from a function.

Except that bit check after each call is required which is neglectable for some function calls, but it's summing up rapidly for the whole amount of modularization. Further, the space for the return value in the caller needs to be widened in some cases.

Only if you want to use traditional dynamically-allocated exceptions. If you only need error codes, no polymorphism is needed.

Checking the bit flag is runtime polymorphism, checking the type field against the catches is runtime polymorphism, checking what the typeid tells about the context type is runtime polymorphism. Checking the type of information behind the context pointer in case of non error codes is runtime polymorphism. The only difference is it is coded somewhat more low level and is a bit more compact than a class object. What if we use structs for exceptions where the first field is the type and the second field the string message pointer/or error code?


The traditional implementation of stack unwinding bypasses normal function return mechanisms. It's basically a glorified longjmp() to the catch block, augmented with the automatic destruction of any objects that might need destruction on the way up the call stack.

It depends. There are two ways I know, either jumping or decrementing the stack pointer and read out the information in the exception tables.



Turns out, the latter is not quite so simple in practice. In order to properly destroy objects on the way up to the catch block, you need to store information about what to destroy somewhere.

I can't imagine why this is different in your case, this is generally the problem of exception handling independent of the underlying mechanism. Once the pointer of the first landing pad is known, the control flow continues as known before until the next error is thrown.
You also need to know where the catch blocks are so that you know where to land. Once you land, you need to know how to match the exception type to what the catch block expects, etc.. To implement this, every function needs to setup standard stack frames so that libunwind knows how to unwind the stack.

Touché, that's better in case of error returns.

It also requires exception tables, an LSDA (language-specific data area) for each function, personality functions, etc.. A whole bunch of heavy machinery just to get things to work properly.


Why would you want to insert it into a list? The context field is a type-erased pointer-sized value. It may not even be a pointer.


Good point, I don't know if anyone tries to gather errors in an intermediate list which is passed to certain handlers. Sometimes exceptions are used as control flow elements though that isn't good practice.


It's not about class vs. non-class (though Error being a struct rather than a class is important for @nogc support). It's about how exception throwing is handled. The current stack unwinding implementation is too heavyweight for what it does; we want it replaced with something simpler and more pay-as-you-go.

I agree, that fast exceptions are worthwhile for certain areas as opt-in, but I don't want them to replace non-fast exceptions because of the runtime impact of normal running code.

That's the whole point of Sutter's proposal: they are all unified with the universal Error struct. There is only one "backend": normal function return values, augmented as a tagged union to distinguish between normal return and error return. We are throwing out nonlocal jumps in favor of normal function return mechanisms. We are throwing out libunwind and all the heavy machinery it entails.

This is about *replacing* the entire exception handling mechanism, not adding another alternative (which would make things even more complicated and heavyweight for no good reason).

Oh, no please not. Interestingly we don't use longjmp in default exception handling, but that would be a good alternative to Herb Sutter’s proposal because exceptions are likewise faster, but have likewise an impact on normal running code in case a new landing pad have to be registered. But interestingly, the occurrence of this is much more seldom than checking the return value after each function.



Reply via email to