Exception Annotations: Lay of the Land
I don’t know why the invariant that toException must drop the context was added. Maybe it was added to make the behaviour of existing exception-rethrowing code outside of base more uniform?
To recap. In modern base, we have that specialized rethrowIO function that does the right thing™ to propagate the context when rethrowing. All rethrowing code in base will use it. However, outside of base, we’ll have lots of code like
catchAndThrow :: IO a -> IO a
catchAndThrow = handle $ \(e :: SomeException) -> throwIO e
catchAndThrowIOException :: IO a -> IO a
catchAndThrowIOException = handle $ \(e :: IOException) -> throwIO e
that performs rethrows using the old throwIO. Rethrows of SomeException, but also rethrows of some concrete exception like IOException.
If we don’t require toException to drop the exception context:
The exception that comes out of
catchAndThrowwill have two backtraces, the one from the originalSomeExceptionand the one added by thethrowIO. Is that bad? Perhaps we could tolerate it and, by convention, assume that the last backtrace in the exception context is the original one. But perhaps it would confuse tooling like the new debugger. As code begins to migrate torethrowIO, double backtraces would become rarer.The exception that comes out of
catchAndThrowIOExceptionwill have only one backtrace, the one added by thethrowIO, because concrete exceptions don’t carry a context. We would need to switch torethrowIOto keep the original backtrace. Is that lack of uniformity with respect toSomeExceptionbad?
Alternatively, if we require toException to drop the exception context, in both catchAndThrow and catchAndThrowIOException the outgoing exception will only have the context added by the throwIO. Again, we would need to switch to rethrowIO to keep the original backtrace instead. And I would prefer toException to drop the context even for ExceptionWithContext , and have that separate toExceptionPreservingContext function mentioned earlier.
SomeExceptionandExceptionWithContextrethrows withthrowIOwon’t include the original context. That would admittedly be a bit unintuitive.- But it’s more uniform with re-throwing other types of exceptions using
throwIO. A single “if you want the original context, switch torethrowIO!” slogan applicable in all cases, instead of “well, you can switch torethrowIO, but in the particular case ofSomeExceptionandExceptionWithContextyou might not need to do so, it depends…” - Duplicate backtraces won’t ever happen.
Discussion in the ATmosphere