This is the second part of the Types and Programming Languages series. For your convenience you can find other parts in the table of contents in Part 1 — Do not return in finally
Last time we saw what happens when we return in finally and that we shouldn’t do it. Today we explore a similar case of exception while handling exception. Let’s take this code in C#:
1 2 3 4 5 6 7 8 9 |
try{ try{ throw new Exception("Exception 1"); }finally{ throw new Exception("Exception 2"); } }catch(Exception e){ Console.WriteLine(e); } |
What’s the output?
This question is a bit tricky. First, there are two exceptions in place and we know that typically various languages (including .NET platform) implement a two-pass exception system. First pass traverses the stack and looks for some handler capable of handling the exception, then second pass unwinds the stack and executes all finally blocks. But what if we throw exception in the second pass?
That depends and differs between languages. For instance, C# loses the exception, as specified by C# language specification:
1 |
If the finally block throws another exception, processing of the current exception is terminated. |
Python 2 does the same, but Python 3 in PEP 3134 changes that:
1 2 3 4 5 |
The proposed semantics are as follows: 1. Each thread has an exception context initially set to None. 2. Whenever an exception is raised, if the exception instance does not already have a __context__ attribute, the interpreter sets it equal to the thread's exception context. 3. Immediately after an exception is raised, the thread's exception context is set to the exception. 4. Whenever the interpreter exits an except block by reaching the end or executing a return, yield, continue, or break statement, the thread's exception context is set to None. |
It’s worth noting that some languages provide a field in the exception class which is supposed to store the previous one but if it’s not set automatically by the platform then the original problem still exists. What’s more, if that field is read only then it’s hard to fix the issue in place.
This is important when handling resources. Some languages provide a construct try with resources
, for instance Java:
1 2 3 |
try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } |
If it was implemented like this:
1 2 3 4 5 6 |
BufferedReader br = new BufferedReader(new FileReader(path)); try { return br.readLine(); } finally { if (br != null) br.close(); } |
then exception thrown in finally block would erase the previous one. This is for instance how it’s implemented in C#. Java does it right, though.