Designing Exception Handling Part 1
Programming languages and libraries came up with many strategies to deal with errors occurring at runtime. Sadly, a famous one is…
Photo by David Pupăză on Unsplash
Designing Exception Handling Part 1
Programming languages and libraries came up with many strategies to deal with errors occurring at runtime. Sadly, a famous one is completely ignoring that things could go wrong. Sometimes, that’s even done deliberately because “it is faster”. In reality it isn’t. It just makes software unreliable and ultimately unusable.
So, if our code must handle errors gracefully, what notable strategies are there?
Status codes are the simplest form. They are loved by lazy language authors because they are easy to implement. The language simply doesn’t offer anything. The programmer has to check the status after each operation that could fail. Each operation has to report status after each step. A most noteworthy thing about status codes is that it is easy to write micro benchmarks that make look them efficient. However, real software consists of many layers of abstractions and libraries and propagating status between them just to inform all parts that everything is fine is pretty expensive. In practice, they simply do not scale.
That’s why most modern languages and libraries use exceptions. Basically, code is executed as if no error would occur. If an error occurs, regular execution stops and special tables in the binary are searched for a piece of code that deals with the situation. The benefit is that there is no code executed on the happy path to check if everything is still fine. Also, there is no memory and registers required for status reporting. But. And that’s a big but: It is really complex to implement it correctly in a non trivial language. This concept interacts with essentially all other control flow and some parts of resource management. Also, there are some historic decisions that still have an impact. And, finally, it is almost impossible to seamlessly integrate exception handling happening across language boundaries. Still, this is the state of the art as it allows for efficient and correct code at scale.
Lastly, there are obscure and specialized mechanisms like registration of callbacks executed on error or termination. Sometimes they are used to allow clients to fix issues and continue. Sometimes they are just meant to allow clients to cleanup their resources before quitting or crashing. Also in very low level scenarios there can be “jumping around” solutions that I won’t go into detail. Simply, because even if you just interact with them as a user you should know much more about the topic than I would write about in such an article.
C++ exceptions
C++ did a great job pioneering modern language concepts. In exception handling, the dominant solution was also developers under C++’s banner: the Itanium ABI also known as zero cost exceptions. The things is. If you are a pioneer you sometimes have to decide something without any prior experience. Unfortunately, C++ allows any value to be used as exception. You can throw 7 and expect some random user to make sense of it.
This results in a plethora of resource management issues. Most notably, if you throw an instance of an exception class, that instance cannot be stack-allocated. Hence, there must be some memory management. And who is going to do that? Obviously the caller. But, in C++, there is a catch all, i.e. you can write catch(…) to handle anything including 7. How would such a handler release the exception correctly? Even the compiler cannot magically help you, because in C++ you can have different dellocaters like delete, delete[], free or even a custom deallocator.
Finally, in these days, most software uses threads. If we have an exception pointer, are we allowed to use it like any other pointer? I.e. can we propagate it to another thread and delete it there? Can we use it there? Because, if it is allocated on thread local storage and the exception kills the thread, it won’t be valid anymore once the owner of the thread tries to access it. Likewise, if we use thread pooling, we would like the exception to be a result of the work package submitted to the pool. Hopefully, not crashing the thread so that it can be reused for processing other work packages.
Other languages
It seems as if authors of newer languages like Go and Rust had a look at the issues and decided to stick their heads into the sand. Rust is especially interesting as they are proud to be a safe language, but still guide programmers around exceptions because they know that their approach does not work (see Rust error handling and std::panic::catch_unwind).
On the other side of the spectrum, there is Java. In Java, Exceptions behave like any other Object. It just works. They do not have the resource management issue, because they have a garbage collector. For the same reason, they also do not need thread-local storage, here. And, exceptions are classes with inheritance allowing the base class to define a minimum set of properties and error categories to be encoded in the type system, such as NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException or IOException. The only downside is that relying on a garbage collector is not an option for an efficient language. So, the question to be answered in the next part is: How can we design an exception handling mechanism that is as usable as Java’s but works in an efficient language and is at least somewhat compatible with the C++ exception handling ABI.
메타데이터
- post_id
- d5b54a550e7a
- slug
- designing-exception-handling-part-1-d5b54a550e7a
- url
- https://medium.com/@feldentm/designing-exception-handling-part-1-d5b54a550e7a
- canonical_url
- https://medium.com/@feldentm/designing-exception-handling-part-1-d5b54a550e7a
- author_url
- https://medium.com/@feldentm
- status
- ok
- fetched_at
- 2026-06-20 20:29:01