[swift-evolution] Proposal: Typed throws

David Owens II david at owensd.io
Fri Dec 4 18:05:56 CST 2015


I created a pull request for this issue: https://github.com/apple/swift-evolution/pull/26

There was a question on GitHub:

> The proposal seems to be ignoring the negative implementation experience in Java. Why is Swift different?

*Moving the discussion back to the alias*

--

I guess first and foremost, the proposal basically boils down to a syntactical sugar request for what could be considered `Error<T>` and `Result<T, U>` types as the realized return values.

The second fundamental ask is this: give us the ability to handle errors in a way that we can verify all of the potential codepaths with the compiler. 

My experience in Java was many years ago, so I can only speak to what I remember, but what I do remember was the proliferation of exceptions for every little minute thing. In the end, they became a mechanism for control flow instead of for actual error cases.

An example would be a `FileNotFoundException`. Is it really exceptional that a file doesn't exist? No, obviously not. Bad API decisions cannot help you abuse language constructs. 

The other problem seemed to be the shear number of exceptions that would be marked as throwable from any particular method. In the proposal, I specifically call out only allowing a single type. The purpose is to avoid such a proliferation problem.

This is what all of my do-catch code boils down to:

```swift
do {
    try f()
}
catch KnownError.Case1 { }
catch KnownError.ThatsAllOfThem {}
catch { fatalError("Well... if someone else adds another case, I better crash here and hopefully I'll see it before my customers do") }
```

Now, if I chose to use a catch-all `catch` clause, then that's fine. However, Swift forces me to do it today. So my option is to either:

1. Crash the user's app so that a potentially subtle bug doesn't propagate through the app causing a more serious error, or
2. Roll the dice and see what happens. Hopefully I have telemetry data here so I know when these handlers are happening, because the compiler cannot tell me.

If you have another option, I'm willing to hear it.


> On Dec 4, 2015, at 12:54 PM, David Owens II <david at owensd.io> wrote:
> 
> 
>> On Dec 4, 2015, at 11:54 AM, John McCall <rjmccall at apple.com> wrote:
>> 
>>> On Dec 4, 2015, at 11:36 AM, David Owens II <david at owensd.io> wrote:
>>> For the most part, I feel that typed errors are more of a response to how we need to handle errors within Swift today.
>>> 
>>>  var vendingMachine = VendingMachine()
>>>  vendingMachine.coinsDeposited = 8
>>> 
>>>  do {
>>>      try buyFavoriteSnack("Alice", vendingMachine: vendingMachine)
>>>  } catch VendingMachineError.InvalidSelection {
>>>      print("Invalid Selection.")
>>>  } catch VendingMachineError.OutOfStock {
>>>      print("Out of Stock.")
>>>  } catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
>>>      print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
>>>  } catch { fatalError("this is always needed…”) }
>> 
>> But this is printing.  Of course you should be able to generically display an error, but you don’t need static typing for that.  Also, I certainly hope you are not actually repeating all this stuff at every catch site.
> 
> This was a sample from the Swift docs: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID508. I added the missing final `catch` that is required with Swift today.
> 
> The point being, the error enum is fully defined but we still need the final `catch` at the end because there is no type information on the `throws` annotation for `buyFavoriteSnack`. So unlike the case when dealing with enums and switch-statements, we lose all compile-time information about coverage of error states when we could know them. The current implementation of `throws` is the only (as far as I can tell) place in Swift that turns a compile-time validation problem into a run-time validation problem.
> 
> That’s my concern.
> 
> Instead, if we could annotate throws, we could move this into a compile-time validation.
> 
>    enum VendingMachineError: ErrorType {
>        case InvalidSelection
>        case InsufficientFunds(coinsNeeded: Int)
>        case OutOfStock
>    }
> 
>    func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws VendingMachineError {
>        let snackName = favoriteSnacks[person] ?? "Candy Bar"
>        try vendingMachine.vend(itemNamed: snackName)
>    }
> 
> This allows the compiler to validate the only error information leaving this call site is a `VendingMachineError` and it allows all callers to ensure that they are indeed handling the all of the errors for `VendingMachineError`. 
> 
> To me, that is a very practical and pragmatic problem and is fundamentally no different than the treatment of exhaustive switch cases needed when dealing with other enum values.
> 
> Instead, if I turned the `buyFavoriteSnack` into this:
> 
>    enum Error<ErrorType> {
>        case Ok,
>        case Error(ErrorType)
>    }
> 
>    func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) -> Error<VendingMachineError>
> 
> I then get to leverage the compiler to check that I’m indeed handling all of the VendingMachineErrors.
> 
> Yes, I do realize that multiple error types would complicate that, but honestly, I’d rather than the limitation that only a single error type can be propagated up the callstack than to have the ability to have no typed error information at all.
> 
> Of course, I can do this myself, but the language is going to win so I still have to deal with how `throws` is treated within the language as a whole.
> 
> -David
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution



More information about the swift-evolution mailing list