19 ms·
I'm going to claim that the problem being solved here fundamentally comes from attempting to shoehorn early exit into an ancient codebase that wasn't designed f
by comex 10y ago
I'm going to claim that the problem being solved here fundamentally comes from attempting to shoehorn early exit into an ancient codebase that wasn't designed for it, via longjmp. Exceptions can be a handy language feature, but plenty of modern code written in languages like C++ (whose exception support is often eschewed) and Go gets by without them. It just has to be written so that most functions manually propagate error codes they receive, potentially after manually unwinding some core state. C code can be written that way too: the manual cleanup that must be written then extends to mundane buffer freeing, but that's a tractable and local requirement; no need for complicated global reasoning around static buffers and custom allocation schemes.
- blaisio 10y agoYeah, I'm surprised they mentioned using longjmp for "exceptions". I think that just shows the age of the nethack codebase - maybe when it was being written people were trying to find ways of incorporating those error handling ideas into C. I doubt any modern C programmers would seriously consider using it over just returning an error code.
- pabb 10y agoIt's a common idiom, specifically in modern C code. See "C Interfaces and Implementations", one of the best books on the subject of writing reusable C. The author dedicates an entire chapter to exception handling, with longjmp as the primarily facility for it.
- PeCaN 10y agoError codes and longjmp are complementary methods of exception handling. Error codes can't handle every circumstance (well, at least not without undue pain). For example, error codes don't deal with nonblocking i/o or coroutines very well.
- deathanatos 10y ago> plenty of modern code written in languages like C++ (whose exception support is often eschewed) and Go gets by without [exceptions]. I would not consider C++ code that eschews exceptions "modern". To eschew exceptions almost always entails giving up RAII, and if you give up RAII you've done yourself a huge disservice: you're now stuck manually managing memory and resources, and suffering all the bugs that come with that. While RAII does not outright prevent these bugs (which is one of the reasons I'm a huge fan of Rust), coding along its principles greatly reduces the risk that you will run into problems. If you're wondering: giving up exceptions means a constructor has no way to signal failure, as the result of a ctor in C++ is always either an exception, or a constructed object. Most "no exceptions" C++ code I've seen opts to construct what I call "zombie objects"; internally, the object tracks that an error has occurred, and all uses of the real object must be first checked against that internal error flag to ensure the object isn't a zombie (or if it is, then error). The object is effectively a null pointer, and has all the trouble that entails. (I also won't pretend that exceptions aren't without problems; in particular, the argument against them because you can't tell, at a particular point in a function's code, if an exception can occur, or where it would be caught, is completely valid, but no different in many popular languages, such as Python, C#, and in some ways, Java. However, I think the advantages of RAII — and exceptions — outweigh their disadvantages.) > It just has to be written so that most functions manually propagate error codes they receive When I've had to write C, I've found this to be exceptionally tedious to do correctly, and all too easy to ignore.
- ori_b 10y ago> To eschew exceptions almost always entails giving up RAII How so? RAII works just fine when you return errors.
- robotresearcher 10y agoHow about in constructors?
- reverius42 10y agoInit methods is the answer to that (but it potentially forces some additional complexity around initialized/not-initialized state into the other methods).
- p4wnc6 10y agoIn fact, I think managing this extra state, also dealing with complications that could arise from object slicing, were the major reasons given when Martin Sústrik argued he should have originally started out with ZeroMQ in C and not in C++. < http://250bpm.com/blog:4 http://250bpm.com/blog:4 >
- deathanatos 10y agoAs I stated in my original post, these have the same classes of problems as null pointers: you have "constructed" an object whose internal state in invalid; a instance of Foo is either a Foo that init'd successfully or not-a-Foo (that failed to init/isn't initialized; a "zombie object") just as a pointer is either a pointer-to-something or a pointer-to-nothing. This is why you say "it potentially forces some additional complexity around initialized/not-initialized state into the other methods. At least in C++ (and many other OO languages), I find the downsides of having these Jekyll and Hyde classes outweigh any benefits that may come of ignoring exceptions.
- deleted 10y ago[deleted]
- et1337 10y agoCompletely agree, and it's surprising to see this in Nethack because game programmers almost unanimously shun exceptions. In games, you have a small number of functions that can tolerate failure, probably relating to IO. Everything else instantly explodes.
- AbacusAvenger 10y agoBear in mind this is Nethack4 which is a fork of Nethack.
- Narishma 10y agoGames that don't use exceptions tend to do so because of performance concerns. Nethack is a turn-based game, so performance isn't as relevant here.
- adrianratnapala 10y agoI wrote a C helper library that does poor-man's exceptions using setjmp/longjmp https://github.com/adrianratnapala/elm0 https://github.com/adrianratnapala/elm0. Partly because of the malloc/free issue, you don't just "throw and forget" like with real exceptions. Instead you jump a short way up the stack, using it as an extra mechanism for error handling inside a module -- living side by side with error returns. And I found myself using the mechanism quite a lot. To the point where writing a new error-returns based function gave me that itch where I felt this is mistake and I would end up refactoring to the TRY mechanism anyway.
- duaneb 10y agoGo still has (slow, dynamic) unwinding for the express purpose of resource management. While I can't advocate for using it directly, it is enough of a concern to include in the core language.