190

I give it a try to understand new error handling thing in swift 2. Here is what I did: I first declared an error enum:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

And then I declared a method that throws an error (not an exception folks. It is an error.). Here is that method:

func makeMeSandwich(names: [String: String]) throws -> String {
    guard let sandwich = names["sandwich"] else {
        throw SandwichError.NotMe
    }

    return sandwich
}

The problem is from the calling side. Here is the code that calls this method:

let kitchen = ["sandwich": "ready", "breakfeast": "not ready"]

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
}

After the do line compiler says Errors thrown from here are not handled because the enclosing catch is not exhaustive. But in my opinion it is exhaustive because there is only two case in SandwichError enum.

For regular switch statements swift can understands it is exhaustive when every case handled.

Jojodmo
  • 23,357
  • 13
  • 65
  • 107
mustafa
  • 15,254
  • 10
  • 48
  • 57

7 Answers7

312

There are two important points to the Swift 2 error handling model: exhaustiveness and resiliency. Together, they boil down to your do/catch statement needing to catch every possible error, not just the ones you know you can throw.

Notice that you don't declare what types of errors a function can throw, only whether it throws at all. It's a zero-one-infinity sort of problem: as someone defining a function for others (including your future self) to use, you don't want to have to make every client of your function adapt to every change in the implementation of your function, including what errors it can throw. You want code that calls your function to be resilient to such change.

Because your function can't say what kind of errors it throws (or might throw in the future), the catch blocks that catch it errors don't know what types of errors it might throw. So, in addition to handling the error types you know about, you need to handle the ones you don't with a universal catch statement -- that way if your function changes the set of errors it throws in the future, callers will still catch its errors.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch let error {
    print(error.localizedDescription)
}

But let's not stop there. Think about this resilience idea some more. The way you've designed your sandwich, you have to describe errors in every place where you use them. That means that whenever you change the set of error cases, you have to change every place that uses them... not very fun.

The idea behind defining your own error types is to let you centralize things like that. You could define a description method for your errors:

extension SandwichError: CustomStringConvertible {
    var description: String {
        switch self {
            case NotMe: return "Not me error"
            case DoItYourself: return "Try sudo"
        }
    }
}

And then your error handling code can ask your error type to describe itself -- now every place where you handle errors can use the same code, and handle possible future error cases, too.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch let error as SandwichError {
    print(error.description)
} catch {
    print("i dunno")
}

This also paves the way for error types (or extensions on them) to support other ways of reporting errors -- for example, you could have an extension on your error type that knows how to present a UIAlertController for reporting the error to an iOS user.

Khaled Annajar
  • 15,542
  • 5
  • 34
  • 45
rickster
  • 124,678
  • 26
  • 272
  • 326
  • 1
    @rickster: Could you really reproduce the compiler error? The original code compiles without errors or warnings for me. And if an uncatched exception is thrown, the program aborts with `error caught in main()`.- So while all that you have said sounds sensible, I cannot reproduce that behavior. – Martin R Jun 09 '15 at 17:06
  • 5
    Love how you separated error messages in an extension. Really nice way to keep your code clean! Great example! – Konrad77 Jun 11 '15 at 08:22
  • It is highly recommended that you avoid using the forced - `try` expression in production code since it can cause a runtime error and cause your application to crash – Otar Jan 23 '19 at 20:02
  • @Otar good thought in general, but it’s a little off topic — the answer doesn’t address using (or not using) `try!`. Also, there arguably are valid, “safe” use cases for the various “force” operations in Swift (unwrap, try, etc) even for production code — if through precondition or configuration you’ve reliably eliminated the possibility of failure, it could be more reasonable to short-circuit to instant failure than write error-handling code that’s untestable. – rickster Jan 23 '19 at 20:22
  • If you all you need is displaying the error message, putting that logic inside `SandwichError` class makes sense. However, I suspect for most errors, the error handling logic cannot be so encapsulated. This is because it usually requires the knowledge of the caller's context (whether to recover, or retry, or report a failure upstream, etc.). In other words, I suspect the most common pattern would have to be to match against specific error types anyway. – max Dec 25 '19 at 09:05
31

I suspect this just hasn’t been implemented properly yet. The Swift Programming Guide definitely seems to imply that the compiler can infer exhaustive matches 'like a switch statement'. It doesn’t make any mention of needing a general catch in order to be exhaustive.

You'll also notice that the error is on the try line, not the end of the block, i.e. at some point the compiler will be able to pinpoint which try statement in the block has unhandled exception types.

The documentation is a bit ambiguous though. I’ve skimmed through the ‘What’s new in Swift’ video and couldn’t find any clues; I’ll keep trying.

Update:

We’re now up to Beta 3 with no hint of ErrorType inference. I now believe if this was ever planned (and I still think it was at some point), the dynamic dispatch on protocol extensions probably killed it off.

Beta 4 Update:

Xcode 7b4 added doc comment support for Throws:, which “should be used to document what errors can be thrown and why”. I guess this at least provides some mechanism to communicate errors to API consumers. Who needs a type system when you have documentation!

Another update:

After spending some time hoping for automatic ErrorType inference, and working out what the limitations would be of that model, I’ve changed my mind - this is what I hope Apple implements instead. Essentially:

// allow us to do this:
func myFunction() throws -> Int

// or this:
func myFunction() throws CustomError -> Int

// but not this:
func myFunction() throws CustomErrorOne, CustomErrorTwo -> Int

Yet Another Update

Apple’s error handling rationale is now available here. There have also been some interesting discussions on the swift-evolution mailing list. Essentially, John McCall is opposed to typed errors because he believes most libraries will end up including a generic error case anyway, and that typed errors are unlikely to add much to the code apart from boilerplate (he used the term 'aspirational bluff'). Chris Lattner said he’s open to typed errors in Swift 3 if it can work with the resilience model.

Community
  • 1
  • 1
Sam
  • 4,694
  • 2
  • 36
  • 47
  • Thanks for the links. Not persuaded by John though: "many libraries include 'other error' type" does not mean that everybody needs an "other error" type. – Franklin Yu Jun 17 '16 at 06:09
  • The obvious counter is that there's no simple way to know what kind of error a function will throw, until it does, forcing the developer to catch all errors and try to handle them as best as possible. It's rather annoying, to be quite frank. – William T Froggard Dec 31 '16 at 22:32
3

Swift is worry that your case statement is not covering all cases, to fix it you need to create a default case:

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch Default {
    print("Another Error")
}
Icaro
  • 14,585
  • 6
  • 60
  • 75
  • 2
    But isn't that awkward? I only have two cases and all of them listed in `catch` statements. – mustafa Jun 08 '15 at 23:20
  • It is apple trying to be super safe as in the future you can add a new entry in the array and forget to add in the case statement – Icaro Jun 08 '15 at 23:23
  • 2
    Now is a good time for an enhancement request that adds `func method() throws(YourErrorEnum)`, or even `throws(YourEnum.Error1, .Error2, .Error3)` so you know what can be thrown – Matthias Bauch Jun 09 '15 at 20:21
  • 9
    The Swift compiler team at one of the WWDC sessions made it clear they didn’t want pedantic lists of all possible errors ‘like Java’. – Sam Jun 13 '15 at 01:38
  • 4
    There is no Default/default error; just leave a blank catch {} as other posters have pointed out – Opus1217 Dec 25 '15 at 22:20
  • 2
    @Icaro That does not make me safe; if I "add a new entry in the array" in the future, the compiler should yell at me for not updating all affected catch clauses. – Franklin Yu Jun 17 '16 at 06:12
  • @FranklinYu Yes it will be following in the default case however you will not have the new message until you add it in the cases – Icaro Jun 19 '16 at 22:51
  • 2
    @Icaro You mean I should wait for the runtime check? Instead of having the compiler telling me that I need to catch one more case? I prefer the latter (compile-time error). – Franklin Yu Jun 20 '16 at 03:05
3

I was also disappointed by the lack of type a function can throw, but I get it now thanks to @rickster and I'll summarize it like this: let's say we could specify the type a function throws, we would have something like this:

enum MyError: ErrorType { case ErrorA, ErrorB }

func myFunctionThatThrows() throws MyError { ...throw .ErrorA...throw .ErrorB... }

do {
    try myFunctionThatThrows()
}
case .ErrorA { ... }
case .ErrorB { ... }

The problem is that even if we don't change anything in myFunctionThatThrows, if we just add an error case to MyError:

enum MyError: ErrorType { case ErrorA, ErrorB, ErrorC }

we are screwed because our do/try/catch is no longer exhaustive, as well as any other place where we called functions that throw MyError

greg3z
  • 601
  • 6
  • 18
  • 5
    Not sure I follow why you’re screwed. You’d get a compiler error, which is what you want, right? That’s what happens to switch statements if you add an enum case. – Sam Jun 14 '15 at 11:46
  • In a sense it seemed most likely to me that this would happen with error enums / do case, but it's exactly as it would happen in enums / switch, you're right. I'm still trying to convince myself that Apple's choice to not type what we throw is the good one, but you don't help me on this one! ^^ – greg3z Jun 15 '15 at 12:30
  • Manually typing thrown errors would end up as a big mess in non-trivial cases. The types are a union of all possible errors from all throw and try statements within the function. If you’re manually maintaining error enums this will be painful. A `catch {}` at the bottom of every block is arguably worse though. I’m hoping the compiler will eventually infer error types automatically where it can but I haven’t been able to confirm. – Sam Jun 16 '15 at 05:44
  • I agree the compiler should, theoretically, be able to infer the error types a function throws. But I think it makes sense also for the dev to explicitly write them down for clarity. In the non-trivial cases you're talking about, listing the different error types seems ok to me: func f() throws ErrorTypeA, ErrorTypeB {} – greg3z Jun 17 '15 at 06:44
  • There is definitely a big part missing in that there’s no mechanism to communicate error types (other than doc comments). However, the Swift team have said they don’t want explicit lists of error types. I’m sure most people who’ve dealt with Java checked exceptions in the past would agree – Sam Jun 17 '15 at 09:52
  • @Sam I have dealt with Java checked exceptions, and I admit that it is painful. But this is not solving the problem; they are running away from it. – Franklin Yu Jun 17 '16 at 06:23
2
enum NumberError: Error {
  case NegativeNumber(number: Int)
  case ZeroNumber
  case OddNumber(number: Int)
}

extension NumberError: CustomStringConvertible {
         var description: String {
         switch self {
             case .NegativeNumber(let number):
                 return "Negative number \(number) is Passed."
             case .OddNumber(let number):
                return "Odd number \(number) is Passed."
             case .ZeroNumber:
                return "Zero is Passed."
      }
   }
}

 func validateEvenNumber(_ number: Int) throws ->Int {
     if number == 0 {
        throw NumberError.ZeroNumber
     } else if number < 0 {
        throw NumberError.NegativeNumber(number: number)
     } else if number % 2 == 1 {
         throw NumberError.OddNumber(number: number)
     }
    return number
}

Now Validate Number :

 do {
     let number = try validateEvenNumber(0)
     print("Valid Even Number: \(number)")
  } catch let error as NumberError {
     print(error.description)
  }
Yogendra Singh
  • 2,063
  • 25
  • 20
0

Error can be handle using switch case in catch

func  checkAge(age:Int) throws {

    guard !(age>0 && age < 18) else{
        throw Adult.child
    }

    guard !(age >= 60) else{
        throw Adult.old
    }
    
    guard (age>0) else{
        throw Adult.notExist
    }
    
}


   do{
       try checkAge(age:0)
      
    }
    catch let error {
        switch error{
        case Adult.child : print("child")
        case Adult.old : print("old")
        case Adult.notExist : print("not Exist")
        default:
            print("default")
        }
    }

enum Adult:Error {
    case child
    case old
    case notExist
}
cigien
  • 57,834
  • 11
  • 73
  • 112
anuj
  • 1
  • 2
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 19 '22 at 07:56
-2

Create enum like this:

//Error Handling in swift
enum spendingError : Error{
case minus
case limit
}

Create method like:

 func calculateSpending(morningSpending:Double,eveningSpending:Double) throws ->Double{
if morningSpending < 0 || eveningSpending < 0{
    throw spendingError.minus
}
if (morningSpending + eveningSpending) > 100{
    throw spendingError.limit
}
return morningSpending + eveningSpending
}

Now check error is there or not and handle it:

do{
try calculateSpending(morningSpending: 60, eveningSpending: 50)
} catch spendingError.minus{
print("This is not possible...")
} catch spendingError.limit{
print("Limit reached...")
}
Mr.Javed Multani
  • 12,549
  • 4
  • 53
  • 52