[swift-evolution] Proposal: Typed throws

David Owens II david at owensd.io
Fri Dec 4 14:54:56 CST 2015


> 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


More information about the swift-evolution mailing list