Swift Optionals and Error Handling: if let, guard and try

iOS App Development with Swift and SwiftUI: Beginner to Job-Ready Projects · Swift Basics · 6 min read · Updated

Safe unwrapping

  1. An optional holds a value or nothing. The type says so with a question mark.
  2. if let and guard let open the box and check it before you use it.
  3. When there is a value, your code runs with a plain, safe value.
  4. When it is nil, the else path or a default with ?? takes over, and the app does not crash.

Optionals are the Swift feature that surprises people most, and the one that makes Swift apps crash less often. Many languages have a value called null or nil that sneaks into your code and crashes it later. Swift makes the possibility of *no value* part of the type, so the compiler makes you deal with it before the app runs. This lesson also covers errors, the other thing that can go wrong in real apps.

What an optional is

An optional is a box that holds either a value or nothing. You write it by adding a question mark to a type. Int? means *an Int, or nothing*. The nothing is written nil. A plain Int can never be nil.

var age: Int? = nil
print(age)          // nil
age = 30
print(age)          // Optional(30)

let text = "42"
let number = Int(text)     // Int? because the text might not be a number
print(number)              // Optional(42)
print(Int("abc"))          // nil

Int(text) is a good example. Not every piece of text is a number, so the result is an optional. You cannot use an Int? where an Int is needed until you open the box, and Swift gives several safe ways to do that.

Unwrapping safely

let input: String? = "Asha"

// 1. if let: use the value only when it exists
if let name = input {
    print("Hello, \(name)")
} else {
    print("No name")
}

// 2. guard let: leave early when it does not exist
func greet(_ input: String?) {
    guard let name = input else {
        print("No name")
        return
    }
    print("Hello, \(name)")   // name is usable for the rest of the function
}

// 3. ?? gives a default
let shown = input ?? "Guest"

// 4. optional chaining: stop quietly at the first nil
let length = input?.count     // Int?
  • if let is best when you only want to do something extra if a value is there.
  • guard let is best at the top of a function. It keeps the main path of the code flat and easy to read, and the else part must leave with return, throw or similar.
  • ?? is best when there is a sensible default.
  • ?. is best when you want to read through a chain, and a missing link should simply give nil.

The danger of the exclamation mark

Adding ! after an optional is called force unwrapping. It says *I promise this is not nil*. If you are wrong, the app crashes at once. People use it in examples to keep them short, and then in real code to get past an error message. Avoid it. A crash is the worst experience for a user, and it is also very easy to avoid. The few places where ! is acceptable, such as a value that your own set-up has already filled in, should be rare and you should be able to explain each one.

let value = Int("abc")
// print(value!)    // crash: found nil while unwrapping

print(value ?? 0)   // safe

Errors: throw, try and catch

Not every problem is a missing value. A file may not open. A web call may fail. A user may type an amount below zero. Swift uses errors for these. An error is a value of any type that conforms to the Error protocol, usually an enum. A function that can fail is marked throws, and the caller must write try before it.

enum ExpenseError: Error {
    case emptyTitle
    case invalidAmount(Double)
}

func makeExpense(title: String, amount: Double) throws -> Expense {
    guard !title.isEmpty else { throw ExpenseError.emptyTitle }
    guard amount > 0 else { throw ExpenseError.invalidAmount(amount) }
    return Expense(title: title, amount: amount)
}

do {
    let e = try makeExpense(title: "Tea", amount: -3)
    print(e.title)
} catch ExpenseError.emptyTitle {
    print("Please enter a title")
} catch ExpenseError.invalidAmount(let value) {
    print("\(value) is not a valid amount")
} catch {
    print("Something else went wrong: \(error)")
}

A do block runs the risky code. If anything inside throws, Swift jumps to the first matching catch. The last bare catch handles everything else, and inside it a value called error is available. You also meet two shortcuts: try? gives an optional (nil if it failed) and try! crashes on failure, so treat it like the exclamation mark.

Errors or optionals?

  • When *nothing there* is a normal answer, such as looking up a key, use an optional.
  • When something *went wrong* and the caller should know why, throw an error.
  • When you build a screen, turn errors into something a person can read. A friendly message and a way to try again beat a technical text.

Your task

  1. Write parseAmount(_ text: String) -> Double? using Double(text). Test it with "12.5" and "abc".
  2. Write a function with guard let that prints a greeting only when a name is given.
  3. Add a ExpenseError.tooLarge case and make makeExpense throw it when the amount is over 10,000.
  4. Catch each error in a do block and print a clear message.

How to check it worked

  • parseAmount("abc") gives nil and does not crash.
  • Each bad input to makeExpense prints its own message.
  • You can name three safe ways to unwrap an optional and say why ! is risky.

Frequently asked questions

What is an optional in Swift?

A type that holds either a value or nothing (nil). It is written with a question mark, such as Int?. The compiler makes you handle the nil case before you use the value.

What is the difference between if let and guard let?

Both unwrap an optional safely. if let uses the value inside its braces. guard let makes the value usable for the rest of the function and must leave the function in its else part.

When should I use try? and try!?

try? turns a failure into nil, which is fine when you do not need the reason. try! crashes if the call fails, so avoid it in real code.