DConf talk : Exceptions will disappear in the future?

sighoya sighoya at gmail.com
Thu Jan 7 00:01:23 UTC 2021


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.





More information about the Digitalmars-d-learn mailing list