> In C++ you can throw an exception, which will unwind the stack properly on
> the way out, and if main() doesn't catch it, then it calls abort(). C has
> no standard equivalent.
> /s/ Adam
Mmmh. So does this mean that C applications have quite a hard time when
they need to quit in a clean way when executing some code with a deep
call stack, right?
Exception handling is a double edged sword, you can cleanly unwind the stack but it may leave your program in a state you can't continue with, because your data structures are left in a state that's undefined. It can also introduce space leaks unless you follow certain rules. Experienced C++ programmers can use the language to write great code, but you tend to make a lot of mistakes before you become an experienced programmer.
Returning an error code and writing explicit error handling code at each level, the thing you typically do in C, may often work better. Google's Go language intentionally left exception handling out of the language.
Error codes are, of course, also a double-edged sword. Exceptions let you know when you've screwed up and you have to go out of your way to ignore them. Error codes have no enforcement mechanism and rely on every layer of every program to be capable of propagating errors upward, and in most languages it's exceptionally (pun not intended) awkward to juggle both error codes and return values. Yes, Go intentionally left out exception handling, but a common complaint about Go is that they didn't always make sure they provided replacements for the stuff they intentionally didn't provide, and Go suffers the same problems as C when it comes to error propagation. (But at least it's harder to break things by doing it wrong!)
Exception handling also has a bad reputation from its early years, when the languages providing it didn't yet provide the facilities to actually WRITE exception-safe code in a sensible manner and when computer scientists were still trying to work out what good abstractions and formalisms were. Modern C++ DOESN'T require you to be a guru to do it right if you use the standard tools idiomatically, and the committee is working on a way to have exceptions participate in the static type system so the compiler can catch more mistakes. (Bonus: static exceptions are not just more robust; they're also faster!)
I have to applaud Rust and Haskell for how they approach it: A function that can fail returns a data structure with a conditional baked into it. The code will fail to compile if you don't explicitly either handle or propagate errors, but propagating errors happens by default if you write code idiomatically. (Not necessarily EFFICIENTLY -- an error might end up flowing through a lot of naive processes as if it were valid data before it gets handled. But since you can't actually perform any meaningful operations with an object in an error state, it doesn't hurt anything, and short-circuiting is just an optimization that isn't even really necessary if errors are sufficiently rare.)
To-be-closed variables work nicely with Lua's idiomatic method of error signaling. It's not quite as robust as Rust's approach, but it's pretty darn solid for a dynamically-typed language. Yes, it's nontrivial to escape from a deeply nested call stack while also cleaning up after yourself, but TBC variables make it easier without requiring disruptive changes to the way you would have written idiomatic Lua code previously.