External Publication
Visit Post

Exception Annotations: Lay of the Land

Haskell Community [Unofficial] May 23, 2026
Source

Embarrassingly, I hadn’t considered WhileHandling then thinking about the rethrow behaviours. I was thinking about the possibility of ending up with duplicate Backtraces annotations in the same annotation list.

I’ve created the this repo to experiment (using GHC 9.14.1)

It has three cases:

catchAndThrow :: IO a -> IO a
catchAndThrow action = catch @SomeException action throwIO

-- like 'catchAndThrow', but with a type of exception for which 'toException'
-- preserves context, here we used 'ExceptionWithContext'
catchAndThrow' :: IO a -> IO a
catchAndThrow' action = catch @(ExceptionWithContext SomeException) action throwIO


-- after migrating to 'catchNoPropagate' + 'rethrowIO'
catchAndRethrow :: IO a -> IO a
catchAndRethrow action = catchNoPropagate @(ExceptionWithContext SomeException) action rethrowIO

In the first case, the exception structure looks like

IOException
|
+- WhileHandling
|  |
|  `- IOException
|     |
|     `- Backtrace: HasCallStack backtrace:/  throwIO, called at app/Main.hs:30:7 ...
|
`- Backtrace: HasCallStack backtrace:/  throwIO, called at app/Main.hs:15:62 ...

The original exception is inside the WhileHandling annotation, with the original backtrace. The rethrown exception has the backtrace of the rethrow.

For the second case (the exception whose toException doesn’t clean the context) we have

IOException
|
+- WhileHandling
|  |
|  `- IOException
|     |
|     `- Backtrace: HasCallStack backtrace:/  throwIO, called at app/Main.hs:30:7 ...
|
+- Backtrace: HasCallStack backtrace:/  throwIO, called at app/Main.hs:20:84 ...
|
`- Backtrace: HasCallStack backtrace:/  throwIO, called at app/Main.hs:30:7 ...

The WhileHandling is still there, but the rethrown exception (confusingly?) has two Backtraces annotations.

In the third case, the rethrow is not visible in the exception, we only have the original Backtraces:

IOException
|
`- Backtrace: HasCallStack backtrace:/  throwIO, called at app/Main.hs:30:7 ...

Discussion in the ATmosphere

Loading comments...