To provide context: To date, my assumptions Around Error handling in Swift had been that 'throws' was a less capable imperative counterpart to the more functional style of error handling. Types such as Result, or Combine's Publisher conforming types – which are generic over their Error conforming Failure parameters – were more powerful and therefore better.
Also, as throws
wasn't suitable across async boundaries, I rarely reached for it in my own APIs, preferring to use Result. If I had a dependency which included throwing APIs – for any thrown Error that I was unable to recover – I would wrap the underlying Error within the base Error type for whatever module I was working within and propagate it up.
But after reviewing the concurrency work and the supporting learning materials created for WWDC, firstly it was clear that the async issue with throws has been solved, but also that there seemed to be an emphasis on 'straight line coding' that preferred to break out of the monadic style of functional programming for typical use-cases.
At least, that's the message I received.
The one thing I didn't get though was how would I would now constrain my thrown Error types. Usually, I'd create a base Error type, perhaps conform it to LocalizedError at the application level and, for APIs that could fail, make use of it in a Result or Publisher.
That's when I came across the typed throws discussions – in my efforts to come up with a workaround. There's a lot of interesting stuff in there, but particularly this post by @John_McCall struck a chord with me:
And it's absolutely true in my case.
My search for a solution for typed throws was almost entirely dogmatic. Whilst there are a small subset of problems for which I need a typed throws, for most use cases a non-typed throwing API actually makes more sense for me.
A big reason I avoided using throws was that casting to an existential in a catch clause seemed like a code smell, but is it worse that including an associated value in your module's Error type to for each dependency's own Error type? On balance I decided 'probably not'.
The only thing that I find myself missing is context.
If there is some unknown error that occurs two modules deep, it would be good to know exactly what's occurring at the point the error is thrown – the same way the callstack generated by a fatal error provides context for a crash. That's really the only reason I can think of to embed the previous errors (assuming the error is non recoverable) – and is why I recently suggested adding an underlyingError: Error?
property via a WrappingError
protocol.
If that were provided, or a similar mechanism for providing context in non-fatal error situations, I think the APIs where the need for typed Errors exists would be reduced to the domains described by John in his quoted post above.