| >I don't get that. Call/Return Pairs are balanced at runtime, not lexically. From all possible returns in a function, only a single one runs, and terminates the whole function. Every call site is paired, at runtime, with a single return that will return control to it, and the entire set of candidate returns are known at compile time, and the compiler checks all of this to report dumb mistakes. This is not how Exceptions work, you can throw more than you can catch, and you can catch more than what can ever be thrown (this is generally harmless, just garbage code that never runs). Neither the number nor the types of throw/catch sites are kept in sync statically. >a return can return to multiple different places depending on where the function was called from Every single call site of a function is paired with all the returns of this function. The returns of a different function are paired with the call sites of that function, not the first. Unlike Exceptions, where every catch is a jump destination for every single throw of the same (dynamic) type across every single function beneath the catch on the call stack. That's literally an exponentially larger set than set of all the returns of a single function. >why that would even be a desirable property It's desirable because it gives static gaurantees. Given a throw, you can't even know how much of the stack it would unwind, it can potentially unwind the entire stack if no compatible catch is above it on the call stack*. Exceptions can break through their abstraction boundaries and unwind the stack of a calling component that doesn't know about them. Exceptions are fundamentally dynamically typed Returns that you can forget about. That's as big a footgun as you can get without being deliberately a troll. Given a catch, you can't even know where is all the places that can go to that catch (unlike a call site). Oh, there is the trivial answer of course : Every single function called in the try{...}, and every single function that those functions call, etc etc etc. Again, an intractable thing to reason about. So people don't, but they have to, the throw/catch pair is a single logical feature, their uses must be reasoned about and kept in sync in assumptions and consequences. None of this is how a return works. If Structured Programming advocates saw Exceptions, they would be horrified. Even C's gotos don't allow you to jump out of a function. * : (which, of course, is a dynamic property: in "if(...) then {Do_Something() catch(){...}} else{...}", only one branch will actually catch, one branch can crash the program, and none of this is visible to the compiler at write time) |
No, that is the entire point of exceptions. They unwind the stack up until the level of a matching catch block, if any. Why would you want to know how many stack levels a throw will unwind? When you call a function you don't know how many functions it will call in turn, and when you return a value you don't know if the callee will return it another level up. You shouldn't want to, since this would create and undesired tight coupling which would make the program hard to modify. It's a feature, not a bug.
> If Structured Programming advocates saw Exceptions, they would be horrified.
I'm sure they would, just as they would be horrified by having multiple returns in the same function - never mind horrors like "break" and "continue". The dogma was "single entry single exit". Exceptions does adhere to the "only lexical blocks and function calls" paradigm, but throwing is a form of multiple return, so it breaks with the dogmatic structured paradigm. But I prefer simple and maintainable code to blind dogma.
> Even C's gotos don't allow you to jump out of a function.
Goto actually has its uses in C, for example for resource cleanup. I think "goto harmful" have become a mantra where people use the words but forget the underlying reasoning. Dijkstras problem with goto was not that they jumped to a different place in the progam, his problems was that they didn't adhere to lexical structures and call stack. Exceptions actually allow resource cleanup in a structured way (using "finally" blocks).