Reputation: 15464
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: Error {
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", "breakfast": "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 are only two cases in SandwichError
enum.
For regular switch statements, swift can understand it is exhaustive when every case is handled.
Upvotes: 195
Views: 224692
Reputation: 119420
š” That is because you didn't specify the throwing error type.
ā
You should define the type of catching error, then switch
on the type to make the compiler understand you, like:
do {
let sandwich = try makeMeSandwich(names: kitchen)
} catch let error as SandwichError { // š Get only the specific typed error
switch error {
case .NotMe: // Do something
case .DoItYourself: // Do something
}
}
This way, you are technically ignoring other errors but you can catch them too:
do {
let sandwich = try makeMeSandwich(names: kitchen)
} catch let error as SandwichError {
...
} catch {
// Any other error will be matched by this block
}
ā ļø Note that although the next section is merged in Swift's
main
branch on GitHub, it is still behind the experimental feature flagTypedThrows
(upcomingFullTypedThrows
). So it is NOT available by default (yet)!
Soon you can specify which error type will be thrown by the function in the first place like:
func makeSandwich() throws(SandwichError) -> String { ... }
and catch it later like:
do throws(SandwichError) { try makeSandwich() }
š Here is full detailed information about Typed Throws in Swift.
Upvotes: 1
Reputation: 11
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
}
Upvotes: 1
Reputation: 2241
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)
}
Upvotes: 2
Reputation: 13274
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...")
}
Upvotes: -2
Reputation: 126137
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.
Upvotes: 318
Reputation: 4732
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.
Upvotes: 32
Reputation: 601
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
Upvotes: 3
Reputation: 14845
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")
}
Upvotes: 3