Introduction to Swift error handling
Published: 2021-04-25 22:28:55

*Note: This article is part of the course* **Introduction to iOS using UIKit** *I've given many times in the past. The course was originally in Spanish, and I decided to release it in English so more people can read it and hopefully it will help them.*

# Swift.Error

So far, we've been using optionals and other techniques to represent errors in Swift.

However, Swift has a native, correct way of representing errors, and that way is using custom enum types implementing `Swift.Error`, or simply `Error`.

The `Error` protocol won't ask us to implement any method or property.

Let's suppose we are modeling a banking app, so we could define our errors with a enum like this, where each of its cases will represent a different error, and the enum itself will implement the `Error` protocol.

```swift
enum AccountError: Error {
    case insufficientFunds
    case wrongPinCode
    case wrongAlphabeticCode
}
```

# throws, throw

Once we've defined our `Error` enum, we can implement functions that use those errors. However, in order to do that, we must specify that our function can throw an error. This is done with the `throws` keyword.

```swift
class Account {
    var funds: Double = 0.o

    func deposit(amount: Double) {
        funds += amount
    }

    func extract(amount: Double) throws {
        // ...
    }
}
```

In this case, the `extract` method could throw an error, if we try to extract an amount we don't have in the account.

Let's then check that using a `guard` clause. If we find an error, we need to `throw` it specifying which error it is.

```swift
func extract(amount: Double) throws {
    guard funds >= amount else {
        throw AccountError.insufficientFunds
    }

    funds -= amount
}
```

# do/try/catch

Nice! We can throw errors. Our next challenge is to handle them effectively when we get them.

The first we have to notice is that throwing functions need to be handled in a special way to use them.

Let's start with the code and then we'll explain it:

```swift
let account = Account()
account.deposit(amount: 100)
do {
    try account.extract(amount: 50)
} catch let error { 
    switch error {
    case AccountError.insufficientFunds:
        print("Insufficient funds!")
    default:
        print("Another error.")
    }
}
```

In the previous example we've defined two different regions.

One, inside the `do` block, where we can call throwing functions. Whenever we need to call a function that may throw, we use the `try` keyword.

The second region is defined inside the `catch` block. Inside the `catch` block we can define a custom name for our error. This is done with a `let` binding, followed by the error name. In this case, its called `error`, which is also the default error name, so it's unneeded in this case.

Inside the `catch` block, we can receive our error, which will be of type `Error`, so we can switch on it and do something different for each error case we've defined, and a `default` clause in case we are getting an error we aren't handling specifically.

# try, try?, try!

Let's modify a bit our function so it will return an object of type `Extraction`, which will represent the extraction we're performing.

```swift
struct Extraction {
    let id: String
    let amount: Double

    init(amount: Double) {
        self.id = UUID().uuidString // Random ID string
        self.amount = amount
    }
}
```

And for our `extract` method:

```swift
func extract(amount: Double) throws -> Extraction {
    guard funds >= amount else {
        throw AccountError.insufficientFunds
    }

    funds -= amount
    return Extraction(amount: amount)
}
```

Let's suppose we are only interested in getting our `Extraction` object, and we aren't interested in the specific error we might get. We can get the `Extraction` object directy using any of these two `try` variations:

- **try?**: Returns the throwing function result as an Optional value, which is `nil` in case the function throws.
- **try!**: Returns the throwing function result as a non-optional value, or crashes in case the function throws.

Of course, we must be very cautious when using `try!`. `try?` instead, is much more widely used.

Let's see the three cases.

## try:

```swift
let account = Account()
account.deposit(amount: 100)
do {
    let extraction = try account.extract(amount: 50)
    // `extraction` is non-optional.
} catch let error {
    switch error {
    case AccountError.insufficientFunds:
        print("Insufficient funds")
    default:
        print("Another error")
    }
}
```

## try?

In this case, `extraction` is of type `Extraction?`, optional, and doesn't require a `do-catch` block.

```swift
let extraction = try? account.extract(amount: 50)
```

## try!

In this case, `extraction` is of type `Extraction`, non-optional, and doesn't require a `do-catch` block.

```swift
let extraction = try? account.extract(amount: 50)
```

# Result

Another way of working with errors is by using the `Result` type, included in the standard Swift library.

Remember that an enum could have values associated to each of its cases. Also, an enum can be generic!

`Result` is a generic type with two cases with associated values. The `Result` type is defined *something like* this, and has many methods to work with these two possibilities:

```swift
enum Result<SuccessType, ErrorType: Error> {
    case success(SuccessType)
    case failure(ErrorType)
}
```

This means that `Result` is a type that represents a case in which the operation has been successful, and a second case in which the operation has failed, so we will get the `Error` for that failure.

Of course, `Result` is included in the standard Swift library, so we won't need to define our own `Result` type.

Let's rewrite the `extract` function so it'll return `Result<Extraction, AccountError>`

```swift
func extract(amount: Double) -> Result<Extraction, AccountError> {
    guard funds >= amount else {
        return Result<Extraction, AccountError>.failure(AccountError.insufficientFunds)
    }

    funds -= amount
    let extraction = Extraction(amount: amount)
    return Result<Extraction, AccountError>.success(extraction)
}
```

As it's obvious that the result is of type `Result<Extraction, AccountError>`, so we can avoid writing that type in the `return`:

```swift
func extract(amount: Double) -> Result<Extraction, AccountError> {
    guard funds >= amount else {
        return .failure(AccountError.insufficientFunds)
    }

    funds -= amount
    let extraction = Extraction(amount: amount)
    return .success(extraction)
}
```

Once our function is rewritten this way, we can modify the code that calls the `extract` function.

```swift
let extractionResult = account.extract(amount: 50)

switch extractionResult {
    case .success(let extraction):
        print("Extraction id: \(extraction.id)")
    case .failure(let error):
        switch error {
            case .insufficientFunds:
                print("Insufficient funds")
            case .wrongAlphabeticCode:
                print("Wrong alphabetic code")
            case .wrongPinCode:
                print("Wrong pin code")
        }
}
```