Error Handling
17.1 Defining and Throwing Errors
enum VendingError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
struct VendingMachine {
var inventory = ["Candy": (price: 12, count: 7)]
var coinsDeposited = 0
mutating func vend(_ name: String) throws {
guard let item = inventory[name] else { throw VendingError.invalidSelection }
guard item.count > 0 else { throw VendingError.outOfStock }
guard item.price <= coinsDeposited else {
throw VendingError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
coinsDeposited -= item.price
inventory[name]?.count -= 1
}
}17.2 Handling Errors
var machine = VendingMachine()
do {
try machine.vend("Candy")
} catch VendingError.invalidSelection {
print("Invalid selection")
} catch VendingError.insufficientFunds(let needed) {
print("Insert \(needed) more coins")
} catch {
print("Unexpected error: \(error)") // implicit `error` constant
}
let result = try? machine.vend("Chips") // nil on error
try! loadBundledConfig() // crashes on error (only when failure is impossible)17.3 Typed Throws (Swift 6.0)
Typed throws suit constrained environments (Embedded Swift) and generic code. Untyped throws (any Error) remains the default for most APIs.
enum ParseError: Error { case empty, invalid(String) }
func parseAge(_ text: String) throws(ParseError) -> Int {
guard !text.isEmpty else { throw .empty }
guard let age = Int(text) else { throw .invalid(text) }
return age
}
do {
let age = try parseAge("abc")
} catch {
switch error { // error is ParseError, not `any Error`
case .empty: print("empty")
case .invalid(let s): print("invalid: \(s)")
}
}17.4 Result, rethrows and defer
func fetchUser(id: Int) -> Result<String, ParseError> {
id > 0 ? .success("User \(id)") : .failure(.invalid("\(id)"))
}
switch fetchUser(id: 1) {
case .success(let user): print(user)
case .failure(let error): print(error)
}
let value = try fetchUser(id: 1).get() // convert a Result to throwing
let wrapped = Result { try parseAge("30") } // throwing → Result
let mapped = fetchUser(id: 1).map { $0.uppercased() }
func transform<T>(_ items: [T], _ f: (T) throws -> T) rethrows -> [T] { // throws only if f throws
try items.map(f)
}
func processFile() throws {
let handle = open()
defer { close(handle) } // runs when the scope exits (any path)
try work(with: handle)
}17.5 Error Best Practices
Use enums for domain errors. Add context with associated values. Conform to LocalizedError for user-facing messages (errorDescription).
Don't use try! except for programmer errors. Prefer throws over optionals when the failure reason matters.
Errors vs fatalError/precondition: errors are for recoverable situations. Preconditions are for programmer mistakes.