TL;DR: how to raise a previously caught exception later on, while preserving the original exception's stacktrace.
Since I think this is useful with the Result
monad or computation expression, esp. since that pattern is often used for wrapping an exception without throwing it, here's a worked out example of that:
type Result<'TResult, 'TError> =
| Success of 'TResult
| Fail of 'TError
module Result =
let bind f =
function
| Success v -> f v
| Fail e -> Fail e
let create v = Success v
let retnFrom v = v
type ResultBuilder () =
member __.Bind (m , f) = bind f m
member __.Return (v) = create v
member __.ReturnFrom (v) = retnFrom v
member __.Delay (f) = f
member __.Run (f) = f()
member __.TryWith (body, handler) =
try __.Run body
with e -> handler e
[<AutoOpen>]
module ResultBuilder =
let result = Result.ResultBuilder()
And now let's use it:
module Extern =
let calc x y = x / y
module TestRes =
let testme() =
result {
let (x, y) = 10, 0
try
return Extern.calc x y
with e ->
return! Fail e
}
|> function
| Success v -> v
| Fail ex -> raise ex // want to preserve original exn's stacktrace here
The problem is that the stacktrace will not include the source of the exception (here namely the calc
function). If I run the code as written, it will throw as follows, which gives no information to the origin of the error:
System.DivideByZeroException : Attempted to divide by zero.
at Microsoft.FSharp.Core.Operators.Raise[T](Exception exn)
at PlayFul.TestRes.testme() in D:\Experiments\Play.fs:line 197
at PlayFul.Tests.TryItOut() in D:\Experiments\Play.fs:line 203
Using reraise()
won't work, it wants a catch-context. Obviously, the following kind-a works, but makes debugging harder because of the nested exceptions and could get pretty ugly if this wrap-reraise-wrap-reraise pattern gets called multiple times in a deep stack.
System.Exception("Oops", ex)
|> raise
Update: TeaDrivenDev suggested in the comments to use ExceptionDispatchInfo.Capture(ex).Throw()
, which works, but requires to wrap the exception in something else, complicating the model. However, it does preserve the stacktrace and it can be made into a fairly workable solution.