[swift-evolution] typed throws

Matthew Johnson matthew at anandabits.com
Sat Aug 19 09:00:51 CDT 2017

Sent from my iPad

> On Aug 18, 2017, at 11:24 PM, John McCall <rjmccall at apple.com> wrote:
>>> On Aug 18, 2017, at 11:43 PM, Mark Lilback <mark at lilback.com> wrote:
>>> On Aug 18, 2017, at 2:27 AM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
>>> Even for non-public code.  The only practical merit of typed throws I have ever seen someone demonstrate is that it would let them use contextual lookup in a throw or catch.  People always say "I'll be able to exhaustively switch over my errors", and then I ask them to show me where they want to do that, and they show me something that just logs the error, which of course does not require typed throws.  Every.  Single.  Time.
>> We're doing it in the project I'm working on. Sure, there are some places where we just log the error. But the vast majority of the time, we're handling them. Maybe that's because we're using reactive programming and errors bubble to the top, so there's no need to write that many error handlers. And if I am just logging, either the error doesn't really matter or there is a TODO label reminding me to fix it at some point.
> I'm not saying people only log errors instead of handling them in some more reasonable way.  I'm saying that logging functions are the only place I've ever seen someone switch over an entire error type.
> I keep bringing exhaustive switches up because, as soon as you have a default case, it seems to me that you haven't really lost anything vs. starting from an opaque type like Error.

If the bar for typed errors is going to be exhaustive handling without an "other" / "unknown" then I doubt we will be able to meet it.  That is possible and useful in some kinds of code but will certainly continue to be the exception.  

On the other hand, if the bar is more generally whether typed errors can improve error handling in practice to a sufficient degree to justify the feature I think there is a good chance that they can, given the right design.  

Currently good error handling often requires a lot of time reading documentation, and often a lot of time trying to *find* the right documentation.  All too often that documentation doesn't even exist or is spotty and out of date.  There has been more than one occasion where the only way to get the information I needed was to read the source code of a dependency (thankfully they were open source).  A language is obviously not expected to solve the problem of poor documentation, but it can and should help surface more information regardless of the state of documentation.

Once you have the necessary information it is often necessary to write tedious error analysis code to categorize the error appropriately for the purpose of recovery.

I believe these problems are a significant driver behind the sad state of error handling in many (probably most) apps.  If a library author believes they have sufficient information about usage to categorize errors in a way that will be useful in practice for the majority of their users the language should support the library author in doing this.  Again, this is specifically *not* about providing an exhaustive list of every possible kind of error that might occur. It is about categorizing errors based on anticipated recovery strategy (while still retaining the underlying error information for cases where the catch site requires it).

Types seem like a convenient way to accomplish the goal of categorization.  They are also already in use by at least some of us, but unfortunately the type information is currently discarded by the language.  This is unfortunate for callers and can also lead to bugs where an error that should have been categorized leaks out because it wasn't wrapped as intended.

>>> On Aug 18, 2017, at 3:11 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
>>> The primary goal for me personally is to factor out and centralize code that categorizes an error, allowing catch sites to focus on implementing recovery instead of figuring out what went wrong.  Here’s some concrete sample code building on the example scenario above:
>> I'm using a similar approach. Here is some stripped down code:
>> //error object used throughout project
>> public struct Rc2Error: LocalizedError, CustomStringConvertible, CustomDebugStringConvertible {
>>    /// basic categories of errors
>>    public enum Rc2ErrorType: String, Error {
>>        /// a requested object was not found
>>        case noSuchElement
>>        /// a requested operation is already in progress
>>        case alreadyInProgress
>>        /// problem parsing json, Freddy error is nested
>>        case invalidJson
>>        /// nestedError will be the NSError 
>>        case cocoa 
>>        /// nested error is related to the file system 
>>        case file 
>>        /// a wrapped error from a websocket 
>>        case websocket 
>>        /// a generic network error 
>>        case network 
>>        /// an invalid argument was passed (or parsed from json)
>>        /// wraps an unknown error
>>        case unknown
>>    }
>>    /// the generic type of the error
>>    public let type: Rc2ErrorType
>>    /// the underlying error that caused the problem
>>    public let nestedError: Error?
>>    /// location in source code of where error happened
>>    public let location: String
>> }
> Okay.  I mean, this seems essentially like Swift's Error design.  The comment says you use this type ubiquitously in your project.  The type completely erases any differences between functions in terms of what errors they can throw.  Predictably, it includes an unknown case.  Code that processes values of this type must look essentially exactly like switching over an Error, except that the unknown case involves explicitly matching '.unknown' instead of using 'default'.
>> //a domain-specific error type that will be nested
>> public enum NetworkingError {
>>    case unauthorized
>>    case unsupportedFileType
>>    case timeout
>>    case connectionError(Error)
>>    case canceled
>>    case uploadFailed(Error)
>>    case invalidHttpStatusCode(HTTPURLResponse)
>>    case restError(code: Int, message: String)
>> }
> Okay.  So you're doing semantic tagging of errors — you're communicating out that an error arose during a specific operation.  And having some static enforcement here makes it easier to ensure that you've tagged all the errors.
>> The most common errors don't need a nested error. The call site can figure out how to recover based on this. Using Result<T,E> I can specifically limit what kind of errors are possible from a function without using the wrapper error. E can always be specified as Error to ignore the typed system.
> I see.  So you do have some functions that use a more specific type.
>> It would be great if the native swift error system let you optionally put compiler-enforced constraints on what kind of error can be thrown. Then I can setup handlers for my specific type of error, but the compiler will give an error/warning if I'm not handling a possible Error. A generic catch-all is not proper error handling.
> Do you really check for eight different cases at the call sites that get a NetworkingError, or is there a common predicate that you use?  Is there a reason that predicate cannot be written on Error?
>> And if I'm calling something that doesn't throw type-constrained errors, I can use a generic handler and wrap it up in my own error. But the call site is getting details necessary to recover (if possible) without having to use the runtime to figure out what kind of error it is.
> Can you talk about about why "without using the runtime" is an important requirement to you?  Why is an enum-switch with a catch-all case acceptable but a type-switch with a default unacceptable?
>> I've got a very efficient system set up right now with great error handling. I don't see why the same capability can't exist in the language, especially when you can choose to completely ignore it. Hopefully more people would use it and we'd stop seeing so many "unknown error" dialogs.
> John.

More information about the swift-evolution mailing list