Stack unwinding in Rust
lucumr.pocoo.org
lucumr.pocoo.org
So, as a newcomer to rust, how can I identify which calls could blow up my single-task app? The docs on println! (http://doc.rust-lang.org/std/macro.println!.html) say nothing about this.
I could use task::try everywhere (http://doc.rust-lang.org/guide-tasks.html#handling-task-pani...), but I'd prefer to have an indication of which functions are pure and cannot error (modulo bugs).
FYI, LLVM does actually support SEH on 64bit windows and Rust does actually make use of it on that platform. It's SEH on 32bit windows (which is completely different) that isn't supported.
The code to unwind C-code that follows rbp and rsp is super trivial (and lightning fast). Once you get into the hell of DWARF you're talking tons and tons of cycles. In all cases, dealing with inlined functions is messy and imprecise (one of the reasons I've heard for not relying on rbp/rsp), but when building a runtime it just feels sad to have tossed out the stack frames.
On another note, the DWARF standard is fairly approachable reading: http://dwarfstd.org/doc/DWARF4.pdf
To be honest I don't know enough about the argument for/against to form an opinion. However this article did absolutely nothing to sway me. The article gives a couple of "reasons" as to why stack unwinding is hard, and seems to imply that they wouldn't be an issue with API level error reporting - but doesn't explain how or why or give any reasoning or practical examples.
Of course, if the reason for the abort was memory scrambling that destroyed the registry of cleanup actions, things get messy, which is why it needs to deregister the signal handler.
I'm not keen on having to hijack the signal handler, nor of the global variables needed for the atexit (or equivalent) handler registry.
All things considered, I'd rather language + compiler support for unwinding with user settable cleanup handlers, a/k/a real exceptions.
Linux 3.11 added support for the O_TMPFILE flag to open(2) so it's not even necessary to call unlink(2).
* Put an object in an invalid state
* Perform some operation that might unwind
* Fix the object again
If you unwind while the object is in an invalid state, and its destructor is called during unwinding, you can end up with undefined behavior.From what I understand based on the article and assuming you don't know which C compiler was used, it seems like this would be impossible to properly deal with and produce undefined behavior.
However, since exceptions are not part of the public function API, I think they have to be carefully packaged to ensure that they are only used in cases where catching them makes no sense (that is, for violated invariants, not normal runtime errors). The biggest nontechnical problem with unchecked exceptions in other languages is that they escape the type system and make it impossible to reason about code by looking at function signatures. This makes them unsuitable as general-purpose error reporting mechanisms, even though table-based unwinding is faster in the common case than error propagation through return statements.
Java tried to solve this problem using checked exceptions, which are probably my favorite feature in the whole language, but sadly I appear to be pretty much alone in liking them. Besides the Java community's total rejection of the feature, it was always hampered by the many ways Java offers to subvert it: unchecked exceptions of various kinds, misfeatures like Thread.stop, a standard that allows exceptions to be thrown at literally almost any point in the presence of VM problems, etc.
Personally I generally agree with the article author--exceptions are useful for some projects, but most of the time the best solution is to abort. Not having to worry about exception safety makes writing unsafe code vastly simpler and doesn't force you to write code in ways that can cause missed optimizations. You also don't have to worry about corrupted shared state as you do with thread unwinding, since processes actually have isolated address spaces. You can take advantage of hardware traps on failure modes like division by zero, which are significantly cheaper than exceptions. And, of course, you get faster compile times and smaller binaries (though the latter is not a big deal most of the time). So my feeling is that that should be the default, with an option to opt into using checked exceptions for projects that need it (web servers, Servo).
My other concern with fully supporting C++ exceptions would thus be that I don't think it would be nearly as easy to switch between the abort method and try-catch as between abort and task failure. I can see the argument for it, though--you're already going to have different semantics with abort, and in some cases the performance benefits of landing pads compared to return result propagation and/or process restart are important enough to outweigh the disadvantages.
In the meantime, abort completely cleans up any in-process state--closing network pipes, deleting temporary files, deallocating mmaped memory, and so on. And what it can't clean up can't be relied on anyway, because programs can always be aborted unexpectedly in other ways. Whether it's the OOM killer, various Rust behavior that triggers abort (panicking during unwinding, for example), stack overflow, power loss, or just SIGKILL, a robust program can never rely on its destructors running anyway (and destructors failing to run is explicitly not part of Rust's definition of unsafe).
So ultimately, the reason abort is easier to reason about is that except in a few special cases (like embedded, where you may have complete control of the hardware--but you likely don't want to be using task failure there), your program already has to be designed to expect an abort at literally any time. Aborting may not always be desired behavior, but it never introduces additional cognitive load, or creates unsafety where it didn't already exist, in the way that exceptions do.
In C++, exceptions can cause problems with dangling pointers if used in constructors, I wouldn't be surprised if the same were true in Rust.